Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/add-app-doctor-dependency-automation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/app': patch
---

Check for the presence of a local Dependabot or Renovate configuration file in App Doctor.
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,15 @@ export const RULE_CATALOG: RuleCatalogEntry[] = [
fix: 'Add the missing webhook subscriptions to shopify.app.toml.',
guide: 'https://shopify.dev/docs/apps/webhooks/configuration/mandatory-webhooks',
},
{
id: 'MISSING_DEPENDENCY_SECURITY_AUTOMATION',
title: 'Repository-level dependency management configuration not detected',
severity: 'low',
points: -5,
description:
'Looks for a local Dependabot or Renovate configuration file at the repository root without validating its contents.',
fix: 'Add a Dependabot or Renovate configuration file at the repository root, or verify existing coverage.',
},
{
id: 'EOL_API_VERSION',
title: 'End-of-life API version',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import type {Issue} from '../types.js'
import type {ManifestFile, ScanContext} from './types.js'

export const DEPENDENCY_AUTOMATION_CONFIG_PATHS = [
'.github/dependabot.yml',
'.github/dependabot.yaml',
'renovate.json',
'renovate.jsonc',
'renovate.json5',
'.github/renovate.json',
'.github/renovate.jsonc',
'.github/renovate.json5',
'.gitlab/renovate.json',
'.gitlab/renovate.jsonc',
'.gitlab/renovate.json5',
'.renovaterc',
'.renovaterc.json',
'.renovaterc.jsonc',
'.renovaterc.json5',
]

function hasDeclaredDependencies(manifest: ManifestFile): boolean {
return Object.keys(manifest.dependencies).length > 0 || Object.keys(manifest.devDependencies ?? {}).length > 0
}

function repositoryManifestPath(path: string): string {
return path.replace(/\\/g, '/')
}

/** Dependabot and Renovate configuration is repository-level, so prefer the root manifest. */
function dependencyAutomationFindingFile(manifests: ManifestFile[]): string {
const paths = manifests.map((manifest) => repositoryManifestPath(manifest.path))
const root = paths.find((path) => path === 'package.json')
if (root) return root

return [...paths].sort((left, right) => {
const depthDelta = left.split('/').length - right.split('/').length
return depthDelta === 0 ? left.localeCompare(right) : depthDelta
})[0]!
}

/** File presence is an adoption signal, not proof of valid configuration, execution, or dependency coverage. */
export function scanDependencyAutomation(context: Pick<ScanContext, 'manifests' | 'dependencyAutomation'>): {
issues: Issue[]
unresolvedReason?: string
} {
const manifests = context.manifests.filter(hasDeclaredDependencies)
if (manifests.length === 0) return {issues: []}
const {files, unresolvedReason} = context.dependencyAutomation
if (unresolvedReason) return {issues: [], unresolvedReason}
if (files.length > 0) return {issues: []}

return {
issues: [
{
id: 'MISSING_DEPENDENCY_SECURITY_AUTOMATION',
severity: 'low',
points: -5,
title: 'Repository-level dependency management configuration not detected',
message:
'No recognized Dependabot or Renovate configuration file was found at the repository root. Add dependency update automation there, or verify that an existing integration covers this app. This check only looks for local configuration files; it does not validate their contents or inspect hosted integrations, CI workflows, or execution results.',
location: {file: dependencyAutomationFindingFile(manifests)},
fix: {
automated: false,
description:
'Add a Dependabot or Renovate configuration file at the repository root, or verify existing coverage.',
},
},
],
}
}
12 changes: 10 additions & 2 deletions packages/app/src/cli/services/app-doctor-engine/rules/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import type {Issue, Capabilities, ProjectDetection, Severity, SourceCandidate} from '../types.js'
import type {AppTomlContent, ExtensionInfo, ManifestFile, SourceFile} from '../scanners/types.js'
import type {
AppTomlContent,
DependencyAutomationInputs,
ExtensionInfo,
ManifestFile,
SourceFile,
} from '../scanners/types.js'

export type {AppTomlContent, ExtensionInfo, ManifestFile, SourceFile} from '../scanners/types.js'

Expand Down Expand Up @@ -38,8 +44,10 @@ export interface ScanContext {
extensions: ExtensionInfo[]
/** Source files read for supported non-secret deterministic analysis. */
sourceFiles: SourceFile[]
/** Package manifest files found (package.json, Gemfile, composer.json) */
/** Supported JavaScript package manifests found. */
manifests: ManifestFile[]
/** Local dependency-management configuration and discovery obstacles. */
dependencyAutomation: DependencyAutomationInputs
/** Safely readable repository text evidence available to secret scanning. */
sensitiveFiles: SourceFile[]
/** Detected capabilities */
Expand Down
Loading
Loading