Require explicit package manager for project operations#7242
Require explicit package manager for project operations#7242dmerand wants to merge 1 commit intodonald/package-manager-root-knownfrom
Conversation
|
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.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
Differences in type declarationsWe 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:
New type declarationsWe found no new type declarations in this PR Existing type declarationspackages/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'>;
/**
|
|
Good addition — enforcing an explicit boundary for operations that require a known package manager is the right pattern. A few things to consider: 1. The The test file changes use 2. Should Each call to 3. The test case — what is "no package.json" in CI/test environments? The new test removes |

What
This follow-up continues the same package-manager stack as #7239 and #7241.
Add an explicit project operation boundary for package-manager use, and use it in dependency install and extension generation paths instead of reading
project.packageManagerdirectly.Why
The stack goal is to make callers choose intent, not implementation.
#7241gives callers that already know the project root a more explicit path. This PR applies the same idea to operation-owned callers: install and mutation code should require a usable project package manager instead of letting'unknown'flow into dependency helpers.How
Add
Project.requirePackageManagerForOperations().Use it from:
installAppDependencies(...)When the app root has no
package.json, these operations now fail early with a clearer error instead of passing ambiguous package-manager state deeper into install code.