Skip to content

hs project upload lint/audit warnings are permanent and inaccurate for theme-only CMS projects on Windows #1630

Description

@chrixian

(first time reporting an issue, apologies if something is incorrect)

CLI Version

8.14.0

Node.js Version

24.19.0

Operating System

Windows 10

Description

Running hs project upload on a plain CMS-theme project (no app/ component) always prints two warnings that cannot be resolved, because both checks are bugged independently:

⚠ WARNING Project lint: lint packages not installed for src\theme\themedir. Run `hs project lint` to install them.
⚠ WARNING npm audit: skipped for src\theme\themedir(npm not available in PATH)

Bug 1 — "lint packages not installed" can never be fixed for theme-only projects

validateLintConfigOnUpload (lib/projects/validateLintConfigOnUpload.ts) warns about every package.json in the project:

for (const { dir } of parsedPackageJsons) {
  lintRoots.add(dir);
}
...
if (!areAllLintPackagesInstalled(lintRoot)) { warnMessage = ...lintPackagesNotConfigured... }

But the command that's supposed to fix it — hs project lint --install-missing-deps — only ever operates on UI-Extension component directories. getUieLintablePackageJsonLocations (lib/projects/uieLinting.ts:172-185) filters to:

const UIE_COMPONENTS = [Components[CARDS_KEY], Components[SETTINGS_KEY], Components[PAGES_KEY]];
...
return allLocations.filter(location =>
  uiePackageDirPrefixes.some(prefix => resolvedLocation.startsWith(prefix))
);

A CMS-theme project with no app/cards, app/settings, or app/pages directory produces an empty result here. hs project lint exits near-instantly (exit 0, no eslint invocation, no "nothing to lint" message — looks like it succeeded), having installed nothing. hs project upload's check then warns forever, because the two commands disagree on scope: upload's check is project-wide, lint's fixer is UIE-only.

Repro:

hs project create   # or any existing theme-only project with no app/ dir
hs project lint --install-missing-deps   # exits instantly, installs nothing, no explanation
hs project upload   # still warns "lint packages not installed"

Suggested fix: either scope validateLintConfigOnUpload to the same UIE-only directories hs project lint actually covers, or make hs project lint install/lint eslint config for all project package.json locations (not just UIE ones), or at minimum have hs project lint print "nothing to lint here" instead of silently no-op'ing when its scope is empty.

Bug 2 — "npm not available in PATH" is a false positive caused by a missing shell: true

Source: @hubspot/ui-extensions-dev-server/dist/lib/npmSecurityAudit.js:43 (invoked from hubspot-cli's lib/projects/npmAuditOnUpload.ts):

const { stdout } = await execFileAsync('npm', ['audit', '--json'], {
  cwd: auditRoot,
  encoding: 'utf8',
  maxBuffer: MAX_NPM_AUDIT_JSON_BUFFER,
});

On Windows, npm is npm.cmd/npm.ps1, not a real executable. child_process.execFile (unlike exec) does not go through a shell, and Windows' CreateProcess doesn't consult PATHEXT the way cmd.exe does — so this throws ENOENT regardless of whether npm is on PATH. The catch block maps that straight to exit code 127:

function resolveExecErrorCode(e) {
  if (e.code === 'ENOENT') { return 127; }
  ...
}

...which npmAuditOnUpload.ts reports as "npm not available in PATH" — even on machines where npm audit run directly in the same shell works fine.

Repro:

node -e "require('child_process').execFile('npm', ['-v'], (e) => console.log(e && e.code))"
# → ENOENT, even though `npm -v` works in the same terminal

Suggested fix: pass shell: true to the execFile call, or resolve to npm.cmd explicitly on win32 (what cross-spawn does), consistent with how lib/npm/npmCli.ts elsewhere in this codebase uses exec() and doesn't hit this.

Steps to Reproduce

bug 1:

hs project create   # or any existing theme-only project with no app/ dir
hs project lint --install-missing-deps   # exits instantly, installs nothing, no explanation
hs project upload   # still warns "lint packages not installed"

bug 2

node -e "require('child_process').execFile('npm', ['-v'], (e) => console.log(e && e.code))"
# → ENOENT, even though `npm -v` works in the same terminal

Expected Behavior

I explain above.

Screenshots

No response

Debug Output

No response

Additional Context

No response

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions