Skip to content

Wave/2 sweet updates#3

Merged
YoungMayor merged 7 commits intomainfrom
wave/2-sweet-updates
Apr 2, 2026
Merged

Wave/2 sweet updates#3
YoungMayor merged 7 commits intomainfrom
wave/2-sweet-updates

Conversation

@YoungMayor
Copy link
Copy Markdown
Owner

This pull request introduces Devlink version 0.0.2, featuring a major overhaul with a modernized, interactive developer experience, significant performance improvements, and enhanced documentation. The update includes a new interactive CLI dashboard, a persistent local package store, faster file discovery, improved git integration, and smarter CLI suggestions. The documentation has been completely rewritten for clarity, and several new dependencies have been added to support these features.

Major Feature Additions and Enhancements:

  • Interactive CLI & Dashboard:
    • Devlink now offers a fully interactive, guided CLI experience with a dashboard. Running devlink without arguments launches this new interface, making workflows more intuitive and user-friendly. [1] [2]
    • Smart suggestions for command corrections ("Did you mean?") and improved navigation have been added. [1] [2]
  • Performance & Infrastructure:
    • Switched from glob to fast-glob for significantly faster file discovery, especially in large projects. [1] [2] [3]
    • Integrated npm-packlist for more reliable file inclusion, respecting .gitignore and .npmignore.
    • Added chokidar for watch mode, enabling devlink publish --watch to automatically sync changes. [1] [2] [3]
  • Local Store & Git Integration:
    • All published packages are now tracked in a persistent local store at ~/.mayrlabs/devlink, with commands to manage and browse packages. [1] [2]
    • New git integration commands (devlink git ignore / show) help manage devlink files in .gitignore. [1] [2]
  • Cleanup and Modernization:
    • Transitioned to ESM-native architecture and modernized build tooling. Deprecated legacy commands and cleaned up package.json scripts.
    • Removed support for .devlinkignore and redundant binaries.

Documentation and Developer Experience:

  • Comprehensive Documentation Rewrite:
    • The README.md has been fully rewritten with a new quick start, feature highlights, security notes, and a command reference for easier onboarding.
    • Added a new SUGGESTIONS.md file outlining future improvements and ideas for the project.
    • Updated the changelog to reflect all major enhancements and infrastructure changes in version 0.0.2. [1] [2] [3]
    • License section updated for clarity.

Dependency Updates:

  • New and Updated Dependencies:
    • Added new dependencies to support the interactive CLI, fast file operations, and suggestions: @clack/prompts, fast-glob, chokidar, figlet, picocolors, and string-similarity (with corresponding type packages). [1] [2] [3] [4] [5]
    • Bumped the package version to 0.0.2.

These changes collectively deliver a much more robust, user-friendly, and high-performance local package development tool.

Add an interactive dashboard (run `devlink`) and refactor CLI to support new commands (publish:watch, store, git ignore|show, update-all, etc.). Introduce store management (store.json constant and new store module), increment installation counts on add, and wire publish watch flow. Update copy/publish logic (use npm-packlist without custom ignore handling) and add helpful fuzzy-matching for unknown commands. Add new support files (SUGGESTIONS.md, src/git.ts, src/interactive.ts, src/store.ts), add several dependencies (e.g. @clack/prompts, fast-glob, chokidar, figlet, picocolors, string-similarity) and update package.json/pnpm lock accordingly.
Add release notes for 0.0.2 documenting major enhancements (interactive CLI/dashboard, persistent store, fast-glob discovery, watch mode, git integration, CLI suggestions) and infrastructure cleanups (ESM migration, npm-packlist, removed legacy commands and deprecated ignore). Also bump package.json version to 0.0.2 and fix minor changelog formatting/link issues.
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 @mayrlabs/devlink to 0.0.2 and modernizes the CLI and underlying tooling by adding an interactive dashboard, introducing persistent store metadata, improving file discovery performance, and expanding documentation.

Changes:

  • Added an interactive CLI mode (default when running devlink with no args), plus “did you mean?” suggestions and new git/store CLI flows.
  • Introduced a persistent store.json metadata file (publish/install tracking) and a publish watch mode using chokidar.
  • Switched directory sync discovery to fast-glob, updated docs/changelog, and added several new dependencies.

Reviewed changes

Copilot reviewed 16 out of 17 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
SUGGESTIONS.md Adds a forward-looking roadmap document.
src/update.ts Adds updateAllPackages() wrapper for updating all devlinked packages.
src/sync-dir.ts Replaces glob with fast-glob for faster directory enumeration during sync.
src/store.ts Introduces store.json read/write and basic package/version metadata operations.
src/remove.ts Hooks uninstall flow into store installation count tracking (but currently has a removal logic bug).
src/publish.ts Updates store metadata on publish and adds watch mode (publishPackageWatch).
src/interactive.ts New interactive dashboard for publish/add/update/installations/store/remove workflows.
src/index.ts Adjusts global store path logic and removes legacy ignore file reader.
src/git.ts Adds .gitignore management commands (git ignore / git show).
src/devlink.ts Adds interactive default behavior, new commands, and command-suggestion handling.
src/copy.ts Uses npm-packlist directly for publish file selection; removes .devlinkignore filtering.
src/constants.ts Adds storeFileName constant and removes ignore filename constant.
src/add.ts Increments store installation counter when installing from the store.
README.md Major rewrite for quick-start, interactive mode, and new command reference.
pnpm-lock.yaml Updates lockfile for new dependencies and versions.
package.json Bumps version to 0.0.2 and adds new runtime/dev dependencies.
CHANGELOG.md Adds 0.0.2 changelog entry and minor formatting tweaks.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

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

Comment thread src/remove.ts
Comment on lines 127 to 133
for (const name of packagesToRemove) {
if (!options.retreat) {
if (fs.existsSync(join(devlinkFolder, name))) {
const lockedPackage = lockFileConfig.packages[name];
if (lockedPackage?.version && fs.existsSync(join(devlinkFolder, name))) {
fs.rmSync(join(devlinkFolder, name), { recursive: true, force: true });
decrementInstallation(name, lockedPackage.version);
}
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.

Bug: this loop reads lockedPackage from lockFileConfig.packages after entries were potentially deleted earlier (when !options.retreat). As a result lockedPackage will usually be undefined here, so the .mayrlabs/devlink/<pkg> folder won’t be removed and decrementInstallation won’t run. Capture the package version before deleting it from the lockfile (e.g., build a map of {name -> version} from the original lockfile), or perform the filesystem removal/decrement before mutating lockFileConfig.packages.

Copilot uses AI. Check for mistakes.
Comment thread src/git.ts Outdated
Comment thread src/publish.ts
Comment on lines +151 to +158
watcher.on('all', async (event, path) => {
console.log(`File ${path} ${event}, republishing...`);
try {
await publishPackage(options);
} catch (e) {
console.error('Error during republish:', e);
}
});
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.

publishPackageWatch can trigger overlapping publishPackage() runs when multiple file events fire quickly (chokidar often emits bursts). This can lead to concurrent writes/copies and inconsistent store state. Serialize republish calls (e.g., with an in-flight flag + rerun, or a debounce/throttle queue) and consider closing the watcher on SIGINT/SIGTERM so watch mode exits cleanly.

Suggested change
watcher.on('all', async (event, path) => {
console.log(`File ${path} ${event}, republishing...`);
try {
await publishPackage(options);
} catch (e) {
console.error('Error during republish:', e);
}
});
let isPublishing = false;
let rerunRequested = false;
const republish = async () => {
if (isPublishing) {
// A publish is already in progress; schedule a rerun after it finishes.
rerunRequested = true;
return;
}
do {
rerunRequested = false;
isPublishing = true;
try {
await publishPackage(options);
} catch (e) {
console.error('Error during republish:', e);
} finally {
isPublishing = false;
}
// If rerunRequested was set to true while we were publishing,
// the loop will run again to process one more publish.
} while (rerunRequested);
};
watcher.on('all', (event, path) => {
console.log(`File ${path} ${event}, republishing...`);
void republish();
});
const stopWatching = () => {
console.log('Stopping watch...');
watcher
.close()
.then(() => {
process.exit(0);
})
.catch((err) => {
console.error('Error while closing watcher:', err);
process.exit(1);
});
};
process.on('SIGINT', stopWatching);
process.on('SIGTERM', stopWatching);

Copilot uses AI. Check for mistakes.
Comment thread src/interactive.ts
Comment on lines +228 to +235
if (vAction === 'delete') {
const sure = await confirm({ message: 'Are you sure?' });
if (sure) {
removePackageVersionFromStore(
selectedPackage as string,
selectedVersion as string,
);
note(`Deleted ${selectedPackage}@${selectedVersion}`);
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 “Delete version” action only removes metadata from store.json via removePackageVersionFromStore, but it does not delete the corresponding package files from the global store directory. This will orphan data on disk while the UI reports it deleted. Consider removing the on-disk folder (e.g., getPackageStoreDir(name, version) with fs.rmSync(..., { recursive:true, force:true })) in the same flow, and drop the now-unused getPackageStoreDir import if not needed.

Copilot uses AI. Check for mistakes.
Comment thread src/interactive.ts
Comment on lines +106 to +112
const pkgData = store.packages[selectedPackage as string];
const versions = Object.keys(pkgData.versions).sort().reverse();

const selectedVersion = await select({
message: `Select version for ${pc.cyan(selectedPackage as string)}:`,
options: versions.map((v) => ({ value: v, label: v })),
});
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.

Version selection is sorted lexicographically (Object.keys(...).sort().reverse()), which will misorder semver versions (e.g., 10.0.0 before 2.0.0) and any +sig metadata versions. Sort by publishedAt from the store data or use a semver-aware comparator so the “latest” versions appear first.

Copilot uses AI. Check for mistakes.
Comment thread package.json
Comment on lines +33 to 46
"@clack/prompts": "^1.2.0",
"chalk": "^4.1.0",
"chokidar": "^5.0.0",
"detect-indent": "^6.0.0",
"fast-glob": "^3.3.3",
"figlet": "^1.11.0",
"glob": "^7.1.4",
"ignore": "^5.0.4",
"ini": "^2.0.0",
"npm-packlist": "^5.0.2",
"picocolors": "^1.1.1",
"string-similarity": "^4.0.4",
"yargs": "^16.1.1"
},
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.

New deps introduce maintenance/compatibility risk: string-similarity is marked deprecated in the lockfile, and chokidar@5.0.0 requires Node >= 20.19.0 (see pnpm-lock), which may break installs on commonly used Node versions unless you also declare engines. Also glob/ignore appear unused in src/ after the fast-glob/npm-packlist migration; consider removing unused deps (and @types/glob) or updating usage accordingly.

Copilot uses AI. Check for mistakes.
Comment thread pnpm-lock.yaml

chokidar@5.0.0:
resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
engines: {node: '>= 20.19.0'}
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.

chokidar@5.0.0 declares engines: { node: '>= 20.19.0' }, which can cause installation failures if users run earlier Node 20.x/18.x. Either pin to a chokidar version compatible with your supported Node range, or add an explicit engines field in package.json to match this requirement and document it.

Suggested change
engines: {node: '>= 20.19.0'}
engines: {node: '>=18'}

Copilot uses AI. Check for mistakes.
Comment thread src/devlink.ts Outdated
Comment thread src/publish.ts
Comment on lines +135 to +159
export const publishPackageWatch = async (options: PublishPackageOptions) => {
const { workingDir } = options;
console.log(`Watching for changes in ${workingDir}...`);

await publishPackage(options);

const watcher = chokidar.watch(workingDir, {
ignored: [
'**/node_modules/**',
'**/.git/**',
join(workingDir, 'package.json'), // Avoid loop if version changes? Actually devlink doesn't change version in src usually.
],
persistent: true,
ignoreInitial: true,
});

watcher.on('all', async (event, path) => {
console.log(`File ${path} ${event}, republishing...`);
try {
await publishPackage(options);
} catch (e) {
console.error('Error during republish:', e);
}
});
};
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.

Test coverage: the new watch mode (publish --watch / publishPackageWatch) isn’t covered by the existing publish tests. Add at least an integration test that verifies the watcher triggers a republish on file change (and that publishes are serialized/debounced to avoid concurrent runs).

Copilot uses AI. Check for mistakes.
Comment thread src/interactive.ts Outdated
Append signature suffix to stored package versions and propagate the resulting finalVersion through copy/publish flows. copy.ts now computes a signature-based version suffix, writes manifests to a versioned store dir, and returns finalVersion instead of just a signature. publish.ts consumes finalVersion, uses it for published paths, and returns it. add.ts reads lockfile when restoring and uses the resolved versionToInstall. remove.ts preserves removed versions to correctly remove/decrement installations. Tests updated to expect finalVersion (async hooks, signature-aware version checks) and a new modern.test.ts with store/git integration checks. package.json test script broadened to run all test/*.ts files.
Make the publish watcher reentrant and robust (queue concurrent publishes, add cleanup on SIGINT/SIGTERM, return watcher). Replace string-similarity with a local Levenshtein-based suggestion (add src/suggest.ts) and wire it into command suggestions. Use picocolors for console coloring. Fix store handling: decrement previous installation when adding a new version, sort package versions by publishedAt, and remove package files/dirs when deleting from the store. Harden gitShow regex by escaping entries. Update package.json (engine >=20.19.0) and remove unused dependencies/devDependencies. Add tests and fixtures for watch mode (test/watch.test.ts and tmp-watch store files).
Regenerate pnpm lockfile to remove obsolete/unused package entries and snapshots. The update drops several older or deduplicated packages (examples: chalk, glob@7, string-similarity, concat-map, path-is-absolute, multiple brace-expansion/balanced-match/minimatch versions, and associated @types entries). This is a lockfile-only change resulting from dependency dedupe/cleanup—no source code changes.
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

Copilot reviewed 21 out of 23 changed files in this pull request and generated 8 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

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

Comment thread src/sync-dir.ts
cwd: srcDir,
ignore: ['**/node_modules/**'],
dot: true,
absolute: false,
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.

copyDirSafe previously used glob with nodir: false (i.e., included directories), but the fast-glob call doesn’t set an equivalent option. The rest of the function expects directories to be present in srcList/destList (e.g., to create new directories before copying nested files). Please set onlyFiles: false (and/or an explicit directory-including pattern) so nested directory copies don’t fail with ENOENT when new folders appear.

Suggested change
absolute: false,
absolute: false,
// Include directories as well as files so directory creation logic works correctly.
onlyFiles: false,

Copilot uses AI. Check for mistakes.
Comment thread src/publish.ts
Comment on lines +181 to +189
const cleanup = async () => {
console.log('Closing watcher...');
await watcher.close();
process.exit(0);
};

process.on('SIGINT', cleanup);
process.on('SIGTERM', cleanup);

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.

publishPackageWatch registers SIGINT/SIGTERM handlers and calls process.exit(0) from within the library function. This can unexpectedly terminate callers (including tests/interactive flows) and the signal listeners aren’t removed when the watcher is closed, which can leak listeners across repeated invocations. Consider moving signal handling + exit behavior to the CLI layer, or have publishPackageWatch return a cleanup function / register listeners behind an option and unregister them on watcher.close().

Suggested change
const cleanup = async () => {
console.log('Closing watcher...');
await watcher.close();
process.exit(0);
};
process.on('SIGINT', cleanup);
process.on('SIGTERM', cleanup);
const originalClose = watcher.close.bind(watcher);
const removeSignalHandlers = (handler: () => void) => {
process.off('SIGINT', handler);
process.off('SIGTERM', handler);
};
const cleanup = async () => {
console.log('Closing watcher...');
await watcher.close();
};
const handleSignal = () => {
void cleanup();
};
watcher.close = (async () => {
removeSignalHandlers(handleSignal);
await originalClose();
}) as typeof watcher.close;
process.on('SIGINT', handleSignal);
process.on('SIGTERM', handleSignal);

Copilot uses AI. Check for mistakes.
Comment thread src/devlink.ts
Comment thread src/copy.ts
Comment on lines 155 to 169
@@ -181,52 +164,54 @@ export const copyPackageToStore = async (options: {
filesToCopy
.sort()
.map((relPath) =>
copyFile(
join(copyFromDir, relPath),
join(storePackageStoreDir, relPath),
relPath,
),
copyFile(join(copyFromDir, relPath), join(destDir, relPath), relPath),
),
);
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.

copyPackageToStore computes file hashes up-front to build the signature, but copyFilesToStore then calls copyFile, which hashes each file again (its return value isn’t used). This doubles IO on publish. Consider making copyFile not compute a hash, or reuse the hash results from copyFilesToStore to build the signature so each file is read once.

Copilot uses AI. Check for mistakes.
Comment thread src/interactive.ts
Comment thread src/interactive.ts
Comment thread test/watch.test.ts Outdated
Comment thread src/publish.ts Outdated
Introduce 'publish:watch' to devlink commands and enable workspaceResolve in interactive publish options. Replace direct readFileSync import with fs.readFileSync and remove several unused imports (exec, cancel, equal). Minor formatting change in copy.ts and update tests to remove an unused assertion import.
Remove relPath handling and the getFileHash call from copyFile: it now ensures the destination directory and returns the fsPromises.cp() promise directly (callers updated). Also add onlyFiles: false to fast-glob options in copyDirSafe so directories are included in glob results. These changes avoid redundant hashing during file copy and ensure directory entries are considered by the glob.
@YoungMayor YoungMayor merged commit 7ae3dda into main Apr 2, 2026
2 checks passed
@YoungMayor YoungMayor deleted the wave/2-sweet-updates 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