Skip to content

Add root-known package manager helper#7241

Open
dmerand wants to merge 1 commit intomainfrom
donald/package-manager-root-known
Open

Add root-known package manager helper#7241
dmerand wants to merge 1 commit intomainfrom
donald/package-manager-root-known

Conversation

@dmerand
Copy link
Copy Markdown
Contributor

@dmerand dmerand commented Apr 9, 2026

What

This follow-up continues the same package-manager stack as #7239.

Add a helper for callers that already know the project root, and use it for project metadata and root dependency update paths instead of depending on the broader upward-walk package-manager helper.

Why

This stack is not about reducing Shopify CLI's supported package managers.

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

#7239 handles directory-local execution for function typegen. This PR handles callers that already know the project root.

Even if getPackageManager(...) remains a broad upward-walk helper, callers that already know the project root should not depend on its broader root-finding and fallback behavior.

How

Add getPackageManagerForProjectRoot(...) to inspect a known project root directly.

Use it from:

  • Project.load(...)
  • addResolutionOrOverride(...)
  • local upgrade dependency updates

Also narrow Project.packageManager to project-valid values plus 'unknown' when the app root has no package.json.

@dmerand dmerand requested a review from a team as a code owner April 9, 2026 23:37
Copy link
Copy Markdown
Contributor Author

dmerand commented Apr 9, 2026

@dmerand dmerand force-pushed the donald/function-typegen-package-manager-fix branch from ad4d161 to 9bdcacc Compare April 13, 2026 18:49
@dmerand dmerand force-pushed the donald/package-manager-root-known branch from 821d5db to 25e1f67 Compare April 13, 2026 18:49
@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/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.


readonly directory: string
readonly packageManager: PackageManager
readonly packageManager: ProjectPackageManager | 'unknown'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can you remove unknown and either make the operation sync or force the caller to narrow?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I looked at that. I kept 'unknown' here because Project is still just modeling what’s on disk, and “app root with no package.json” is a real state.

The sync narrowing is in the next PR which adds requirePackageManagerForOperations(), so install/mutation callers have to narrow before using it.

Happy to fold that in here instead if you’d prefer, but I split it to keep metadata vs operation-owned use separate.

* @returns The package manager detected from root markers, defaulting to `npm` when no marker exists.
* @throws PackageJsonNotFoundError if the provided directory does not contain a package.json.
*/
export async function getPackageManagerForProjectRoot(rootDirectory: string): Promise<ProjectPackageManager> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

When you work with the CLI, I think you typically run it from the project root or from an extension. But both of them already have a package.json, so why using this instead of getPackageManager? Isn't it the same?


export async function addResolutionOrOverride(directory: string, dependencies: Record<string, string>): Promise<void> {
const packageManager = await getPackageManager(directory)
const packageManager = await getPackageManagerForProjectRoot(directory)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

How do you know that this directory is the root or not? If it must be, I'd make it explicit with the param name or documentation.

Base automatically changed from donald/function-typegen-package-manager-fix to main April 14, 2026 13:18
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.

3 participants