Skip to content

Use Project follow-up command helper for display-only paths#7243

Open
dmerand wants to merge 1 commit intodonald/package-manager-project-operationsfrom
donald/package-manager-follow-up-commands
Open

Use Project follow-up command helper for display-only paths#7243
dmerand wants to merge 1 commit intodonald/package-manager-project-operationsfrom
donald/package-manager-follow-up-commands

Conversation

@dmerand
Copy link
Copy Markdown
Contributor

@dmerand dmerand commented Apr 10, 2026

What

This follow-up continues the same package-manager stack as #7239, #7241, and #7242.

Add a formatProjectFollowUpCommand(...) helper for Project-backed follow-up commands, and use it in the display-only callers in deploy, dev, info, and generate instead of formatting commands from project.packageManager directly.

Why

The stack goal is to make callers choose intent, not implementation.

#7242 added an explicit package-manager boundary for project operations. This PR applies the same idea to display-only paths: when a caller only needs the right follow-up command for a project, it should ask for that command shape instead of reaching into package-manager state and formatting it itself.

This keeps the slice narrow. It does not change the package-manager values still surfaced for project info output, and it does not reopen the loader/config-link paths.

How

Add formatProjectFollowUpCommand(...) in packages/app/src/cli/utilities/project-command.ts.

Use it from:

  • deploy completion messaging
  • dev scope mismatch warnings
  • info follow-up messaging
  • generate success messaging

@dmerand dmerand force-pushed the donald/package-manager-follow-up-commands branch from 4ed4724 to 17a0158 Compare April 10, 2026 00:35
@github-actions
Copy link
Copy Markdown
Contributor

Differences in type declarations

We detected differences in the type declarations generated by Typescript for this branch compared to the baseline ('main' branch). Please, review them to ensure they are backward-compatible. Here are some important things to keep in mind:

  • Some seemingly private modules might be re-exported through public modules.
  • If the branch is behind main you might see odd diffs, rebase main into this branch.

New type declarations

We found no new type declarations in this PR

Existing type declarations

packages/cli-kit/dist/private/node/ui.d.ts
 import { Logger, LogLevel } from '../../public/node/output.js';
-import React from 'react';
 import { Key, RenderOptions } from 'ink';
 import { EventEmitter } from 'events';
-/**
- * Signal that the current Ink tree is done. Must be called within an
- * InkLifecycleRoot — throws if the provider is missing so lifecycle
- * bugs surface immediately instead of silently hanging.
- */
-export declare function useComplete(): (error?: Error) => void;
-/**
- * Root wrapper for Ink trees. Owns the single `exit()` call site — children
- * signal completion via `useComplete()`, which sets state here. The `useEffect`
- * fires post-render, guaranteeing all batched state updates have been flushed
- * before the tree is torn down.
- */
-export declare function InkLifecycleRoot({ children }: {
-    children: React.ReactNode;
-}): React.JSX.Element;
 interface RenderOnceOptions {
     logLevel?: LogLevel;
     logger?: Logger;
     renderOptions?: RenderOptions;
 }
 export declare function renderOnce(element: JSX.Element, { logLevel, renderOptions }: RenderOnceOptions): string | undefined;
-export declare function render(element: JSX.Element, options?: RenderOptions): Promise<void>;
+export declare function render(element: JSX.Element, options?: RenderOptions): Promise<unknown>;
 export declare class Stdout extends EventEmitter {
     columns: number;
     rows: number;
     readonly frames: string[];
     private _lastFrame?;
     constructor(options: {
         columns?: number;
         rows?: number;
     });
     write: (frame: string) => void;
     lastFrame: () => string | undefined;
 }
 export declare function handleCtrlC(input: string, key: Key, exit?: () => void): void;
 export {};
packages/cli-kit/dist/public/node/node-package-manager.d.ts
@@ -27,6 +27,7 @@ export type DependencyType = 'dev' | 'prod' | 'peer';
  */
 export declare const packageManager: readonly ["yarn", "npm", "pnpm", "bun", "homebrew", "unknown"];
 export type PackageManager = (typeof packageManager)[number];
+export type ProjectPackageManager = Extract<PackageManager, 'yarn' | 'npm' | 'pnpm' | 'bun'>;
 /**
  * Returns an abort error that's thrown when the package manager can't be determined.
  * @returns An abort error.
@@ -59,11 +60,68 @@ export declare class FindUpAndReadPackageJsonNotFoundError extends BugError {
  */
 export declare function packageManagerFromUserAgent(env?: NodeJS.ProcessEnv): PackageManager;
 /**
- * Returns the dependency manager used in a directory.
- * @param fromDirectory - The starting directory
- * @returns The dependency manager
+ * Returns the dependency manager governing the project or workspace root discovered from a
+ * starting directory.
+ *
+ * Use this when the caller does not already know the project root and wants the historical
+ * root-finding behavior based on /home/runner/work/cli/cli.
+ *
+ * @param fromDirectory - The starting directory.
+ * @returns The detected package manager, or a user-agent fallback if the root can't be resolved.
  */
 export declare function getPackageManager(fromDirectory: string): Promise<PackageManager>;
+/**
+ * Resolves the package manager for a directory already known to be the project root.
+ *
+ * Use this when the caller already knows the root directory and wants to inspect that root
+ * directly rather than rediscovering it via /home/runner/work/cli/cli.
+ *
+ * @param rootDirectory - The known project root directory.
+ * @returns The package manager detected from root markers, defaulting to npm <command>
+
+Usage:
+
+npm install        install all the dependencies in your project
+npm install <foo>  add the <foo> dependency to your project
+npm test           run this project's tests
+npm run <foo>      run the script named <foo>
+npm <command> -h   quick help on <command>
+npm -l             display usage info for all commands
+npm help <term>    search for help on <term>
+npm help npm       more involved overview
+
+All commands:
+
+    access, adduser, audit, bugs, cache, ci, completion,
+    config, dedupe, deprecate, diff, dist-tag, docs, doctor,
+    edit, exec, explain, explore, find-dupes, fund, get, help,
+    help-search, init, install, install-ci-test, install-test,
+    link, ll, login, logout, ls, org, outdated, owner, pack,
+    ping, pkg, prefix, profile, prune, publish, query, rebuild,
+    repo, restart, root, run-script, sbom, search, set,
+    shrinkwrap, star, stars, start, stop, team, test, token,
+    undeprecate, uninstall, unpublish, unstar, update, version,
+    view, whoami
+
+Specify configs in the ini-formatted file:
+    /home/runner/work/_temp/.npmrc
+or on the command line via: npm <command> --key=value
+
+More configuration info: npm help config
+Configuration fields: npm help 7 config
+
+npm@11.3.0 /opt/hostedtoolcache/node/24.1.0/x64/lib/node_modules/npm when no marker exists.
+ * @throws PackageJsonNotFoundError if the provided directory does not contain a package.json.
+ */
+export declare function getPackageManagerForProjectRoot(rootDirectory: string): Promise<ProjectPackageManager>;
+/**
+ * Builds the command and argv needed to execute a local binary using the package manager
+ * detected from the provided directory or its ancestors.
+ */
+export declare function packageManagerBinaryCommandForDirectory(fromDirectory: string, binary: string, ...binaryArgs: string[]): Promise<{
+    command: string;
+    args: string[];
+}>;
 interface InstallNPMDependenciesRecursivelyOptions {
     /**
      * The dependency manager to use to install the dependencies.
packages/cli-kit/dist/public/node/ui.d.ts
@@ -34,7 +34,7 @@ export interface RenderConcurrentOptions extends PartialBy<ConcurrentOutputProps
  * 00:00:00 │ frontend │ third frontend message
  *
  */
-export declare function renderConcurrent({ renderOptions, ...props }: RenderConcurrentOptions): Promise<void>;
+export declare function renderConcurrent({ renderOptions, ...props }: RenderConcurrentOptions): Promise<unknown>;
 export type AlertCustomSection = CustomSection;
 export type RenderAlertOptions = Omit<AlertOptions, 'type'>;
 /**

@dmerand dmerand marked this pull request as ready for review April 10, 2026 00:38
@dmerand dmerand requested a review from a team as a code owner April 10, 2026 00:38
Copy link
Copy Markdown
Contributor Author

dmerand commented Apr 10, 2026

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

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