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. |
|
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 |
bfabe0f to
821d5db
Compare
0214c2e to
68a6662
Compare
821d5db to
25e1f67
Compare
ryancbahan
left a comment
There was a problem hiding this comment.
This feels like the control hould be inverted. Rather than the project itself having a method that throws when its property is missing, the caller should check project.packageManager when it needs to and ignore it when it doesn't. If you really need a dedicated helper, you could just have a plain function that takes the project and does the check/narrowing.
68a6662 to
1e6ebeb
Compare
1e6ebeb to
738df5d
Compare
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/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.
@@ -68,6 +69,58 @@ export declare function packageManagerFromUserAgent(env?: NodeJS.ProcessEnv): Pa
* @returns The dependency manager
*/
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 walking upward from a child directory.
+ *
+ * @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.
|
That works for me. I took another pass, wanna give it a look? |

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
requireProjectPackageManagerForOperations(project)inpackages/app/src/cli/utilities/project-package-manager.ts.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.