Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f75e061
feat: npm trust command
reggi Jan 12, 2026
b59bc56
chore: lint fixes
reggi Jan 12, 2026
fdd3c50
chore: fix windows tests failures for line nav.yml endings
reggi Jan 12, 2026
a1995af
chore: fix smoke tests
reggi Jan 12, 2026
ef046ad
fix: docs test coverage
reggi Jan 12, 2026
2cd3281
chore: fix the docs tests + nav gen order
reggi Jan 12, 2026
f0b6373
chore: fix codeql warning
reggi Jan 12, 2026
c0315dd
fix: remove flag warnings at command level
reggi Jan 12, 2026
105268e
fix: positionals
reggi Jan 12, 2026
c7b07f9
chore: config tests
reggi Jan 12, 2026
598cf3a
chore: move trust subcommand to folder
reggi Jan 13, 2026
6131f24
chore: config tests fail when registry is not default
reggi Jan 13, 2026
192aa88
chore: drop trust- file prefix
reggi Jan 13, 2026
294e0bb
fix: use stdout for messaging
reggi Jan 15, 2026
e68edb4
Update lib/base-cmd.js
reggi Jan 26, 2026
691d793
Update lib/trust-cmd.js
reggi Jan 26, 2026
37a709f
usage Error for unknown flags
reggi Jan 27, 2026
030a17e
env
reggi Jan 27, 2026
29be392
put urls on their own line for accessibility
reggi Jan 29, 2026
de3d07a
fix pkg check for list, revoke, fix unknown issue for entity provider
reggi Jan 30, 2026
4f0d324
fix redaction of oidc config id in json output
reggi Feb 3, 2026
4d70fa9
new command
reggi Feb 4, 2026
9b6dc38
snapshot
reggi Feb 4, 2026
f0e84dc
trust doc / table
reggi Feb 5, 2026
c72afc7
snapshot fix
reggi Feb 5, 2026
d1226f3
rm test injection use command loader
reggi Feb 6, 2026
593b05d
remove e401 check
reggi Feb 6, 2026
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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
/DEPENDENCIES.md text eol=lf
/DEPENDENCIES.json text eol=lf
/AUTHORS text eol=lf
/docs/lib/content/nav.yml text eol=lf

# fixture tarballs should be treated as binary
/workspaces/*/test/fixtures/**/*.tgz binary
Expand Down
294 changes: 293 additions & 1 deletion docs/lib/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,281 @@ const parseFrontMatter = require('front-matter')
const checkNav = require('./check-nav.js')
const { DOC_EXT, ...transform } = require('./index.js')

// Helper to check if a directory exists
const dirExists = async (path) => {
try {
const stat = await fs.stat(path)
return stat.isDirectory()
} catch {
return false
}
}

// Helper to read docs from a section directory
const readSectionDocs = async (contentPath, section, orderedUrls) => {
const sectionPath = join(contentPath, section)
if (!await dirExists(sectionPath)) {
return []
}

const files = await fs.readdir(sectionPath)
const docFiles = files.filter(f => f.endsWith(DOC_EXT))

// If no doc files exist, return empty array
/* istanbul ignore if - defensive check for empty directories */
if (docFiles.length === 0) {
return []
}

// Parse each doc file to get title and description from frontmatter
const docs = await Promise.all(
docFiles.map(async (file) => {
const content = await fs.readFile(join(sectionPath, file), 'utf-8')
const { attributes } = parseFrontMatter(content)
const name = basename(file, DOC_EXT)

return {
title: attributes.title,
url: `/${section}/${name}`,
description: attributes.description,
name,
}
})
)

// Preserve order from orderedUrls, append any new files at the end sorted alphabetically
const orderedDocs = []
const docsByUrl = new Map(docs.map(d => [d.url, d]))

// First, add docs in the order they appear in orderedUrls
for (const url of orderedUrls) {
const doc = docsByUrl.get(url)
if (doc) {
orderedDocs.push(doc)
docsByUrl.delete(url)
}
}

return orderedDocs.map(({ name, ...rest }) => rest)
}

// Generate nav.yml from the filesystem
const generateNav = async (contentPath, navPath) => {
const docsCommandsPath = join(contentPath, 'commands')

// Read all command files
const commandFiles = await dirExists(docsCommandsPath) ? await fs.readdir(docsCommandsPath) : []
const commandDocs = commandFiles.filter(f => f.endsWith(DOC_EXT))

// Parse each command file to get title and description
const allCommands = await Promise.all(
commandDocs.map(async (file) => {
const content = await fs.readFile(join(docsCommandsPath, file), 'utf-8')
const { attributes } = parseFrontMatter(content)
const name = basename(file, DOC_EXT)
const title = (attributes.title || name).replace(/^npm-/, 'npm ')

return {
title,
url: `/commands/${name}`,
description: attributes.description || '',
name,
}
})
)

// Sort commands: npm first, then alphabetically, npx last
const npm = allCommands.find(c => c.name === 'npm')
const npx = allCommands.find(c => c.name === 'npx')
const others = allCommands
.filter(c => c.name !== 'npm' && c.name !== 'npx')
.sort((a, b) => a.name.localeCompare(b.name))

// Remove the name field
const commands = [npm, ...others, npx].filter(Boolean).map(({ name, ...rest }) => rest)

// Hardcoded order for configuring-npm section (only urls - title/description come from frontmatter)
const configuringNpmOrder = [
'/configuring-npm/install',
'/configuring-npm/folders',
'/configuring-npm/npmrc',
'/configuring-npm/npm-shrinkwrap-json',
'/configuring-npm/package-json',
'/configuring-npm/package-lock-json',
]

// Hardcoded order for using-npm section (only urls - title/description come from frontmatter)
const usingNpmOrder = [
'/using-npm/registry',
'/using-npm/package-spec',
'/using-npm/config',
'/using-npm/logging',
'/using-npm/scope',
'/using-npm/scripts',
'/using-npm/workspaces',
'/using-npm/orgs',
'/using-npm/dependency-selectors',
'/using-npm/developers',
'/using-npm/removal',
]

// Read actual docs from configuring-npm and using-npm directories
const configuringNpmDocs = await readSectionDocs(contentPath, 'configuring-npm', configuringNpmOrder)
const usingNpmDocs = await readSectionDocs(contentPath, 'using-npm', usingNpmOrder)

// Build the navigation structure - only include sections with content
const navData = []

if (commands.length > 0) {
navData.push({
title: 'CLI Commands',
shortName: 'Commands',
url: '/commands',
children: commands,
})
}

if (configuringNpmDocs.length > 0) {
navData.push({
title: 'Configuring npm',
shortName: 'Configuring',
url: '/configuring-npm',
children: configuringNpmDocs,
})
}

if (usingNpmDocs.length > 0) {
navData.push({
title: 'Using npm',
shortName: 'Using',
url: '/using-npm',
children: usingNpmDocs,
})
}

const prefix = `# This is the navigation for the documentation pages; it is not used
# directly within the CLI documentation. Instead, it will be used
# for the https://docs.npmjs.com/ site.
`
await fs.writeFile(navPath, `${prefix}\n${yaml.stringify(navData, { indent: 2, indentSeq: false })}`, 'utf-8')
}

// Auto-generate doc templates for commands without docs
const autoGenerateMissingDocs = async (contentPath, navPath, commandsPath = null) => {
commandsPath = commandsPath || join(__dirname, '../../lib/commands')
const docsCommandsPath = join(contentPath, 'commands')

// Get all commands from commandsPath directory
let commands
try {
const cmdListPath = join(commandsPath, '..', 'utils', 'cmd-list.js')
const cmdList = require(cmdListPath)
commands = cmdList.commands
} catch {
// Fall back to reading command files from commandsPath
const cmdFiles = await fs.readdir(commandsPath)
commands = cmdFiles
.filter(f => f.endsWith('.js'))
.map(f => basename(f, '.js'))
}

// Get existing doc files
const existingDocs = await fs.readdir(docsCommandsPath)
const documentedCommands = existingDocs
.filter(f => f.startsWith('npm-') && f.endsWith(DOC_EXT))
.map(f => f.replace('npm-', '').replace(DOC_EXT, ''))

// Find commands without docs
const missingDocs = commands.filter(cmd => !documentedCommands.includes(cmd))

// Generate docs for missing commands
const newEntries = []
for (const cmd of missingDocs) {
const Command = require(join(commandsPath, `${cmd}.js`))
const description = Command.description || `The ${cmd} command`
const docPath = join(docsCommandsPath, `npm-${cmd}${DOC_EXT}`)

const template = `---
title: npm-${cmd}
section: 1
description: ${description}
---

### Synopsis

<!-- AUTOGENERATED USAGE DESCRIPTIONS -->

### Description

${description}

### Configuration

<!-- AUTOGENERATED CONFIG DESCRIPTIONS -->

### See Also

* [npm help config](/commands/npm-config)
`

await fs.writeFile(docPath, template, 'utf-8')

// Track new entry for nav update
newEntries.push({
title: `npm ${cmd}`,
url: `/commands/npm-${cmd}`,
description,
})
}

// Update nav.yml if there are new entries
if (newEntries.length > 0) {
const navContent = await fs.readFile(navPath, 'utf-8')
const navData = yaml.parse(navContent)

// Find CLI Commands section
let commandsSection = navData.find(s => s.title === 'CLI Commands')
if (!commandsSection) {
// Create CLI Commands section if it doesn't exist
commandsSection = {
title: 'CLI Commands',
shortName: 'Commands',
url: '/commands',
children: [],
}
navData.unshift(commandsSection)
}

if (!commandsSection.children) {
commandsSection.children = []
}

// Add new entries that don't already exist
for (const entry of newEntries) {
const exists = commandsSection.children.some(c => c.url === entry.url)
if (!exists) {
commandsSection.children.push(entry)
}
}

// Sort children: npm first, then alphabetically, npx last
const npm = commandsSection.children.find(c => c.title === 'npm')
const npx = commandsSection.children.find(c => c.title === 'npx')
const others = commandsSection.children
.filter(c => c.title !== 'npm' && c.title !== 'npx')
.sort((a, b) => a.title.localeCompare(b.title))

commandsSection.children = [npm, ...others, npx].filter(Boolean)

// Write updated nav
const prefix = `# This is the navigation for the documentation pages; it is not used
# directly within the CLI documentation. Instead, it will be used
# for the https://docs.npmjs.com/ site.
`
await fs.writeFile(navPath, `${prefix}\n${yaml.stringify(navData, { indent: 2, indentSeq: false })}`, 'utf-8')
}
}

const mkDirs = async (paths) => {
const uniqDirs = [...new Set(paths.map((p) => dirname(p)))]
return Promise.all(uniqDirs.map((d) => fs.mkdir(d, { recursive: true })))
Expand All @@ -28,7 +303,21 @@ const pAll = async (obj) => {
}, {})
}

const run = async ({ content, template, nav, man, html, md }) => {
const run = async (opts) => {
const {
content, template, nav, man, html, md,
skipAutoGenerate, skipGenerateNav, commandLoader,
} = opts
// Auto-generate docs for commands without documentation
if (!skipAutoGenerate) {
await autoGenerateMissingDocs(content, nav)
}

// Generate nav.yml from filesystem
if (!skipGenerateNav) {
await generateNav(content, nav)
}

await rmAll(man, html, md)
const [contentPaths, navFile, options] = await Promise.all([
readDocs(content),
Expand Down Expand Up @@ -73,6 +362,7 @@ const run = async ({ content, template, nav, man, html, md }) => {
}) => {
const applyTransforms = makeTransforms({
path: childPath,
commandLoader,
data: {
...data,
github_repo: 'npm/cli',
Expand Down Expand Up @@ -145,3 +435,5 @@ const run = async ({ content, template, nav, man, html, md }) => {
}

module.exports = run
module.exports.generateNav = generateNav
module.exports.autoGenerateMissingDocs = autoGenerateMissingDocs
21 changes: 21 additions & 0 deletions docs/lib/content/commands/npm-get.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
title: npm-get
section: 1
description: Get a value from the npm configuration
---

### Synopsis

<!-- AUTOGENERATED USAGE DESCRIPTIONS -->

### Description

Get a value from the npm configuration

### Configuration

<!-- AUTOGENERATED CONFIG DESCRIPTIONS -->

### See Also

* [npm help config](/commands/npm-config)
21 changes: 21 additions & 0 deletions docs/lib/content/commands/npm-ll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
title: npm-ll
section: 1
description: List installed packages
---

### Synopsis

<!-- AUTOGENERATED USAGE DESCRIPTIONS -->

### Description

List installed packages

### Configuration

<!-- AUTOGENERATED CONFIG DESCRIPTIONS -->

### See Also

* [npm help config](/commands/npm-config)
21 changes: 21 additions & 0 deletions docs/lib/content/commands/npm-set.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
title: npm-set
section: 1
description: Set a value in the npm configuration
---

### Synopsis

<!-- AUTOGENERATED USAGE DESCRIPTIONS -->

### Description

Set a value in the npm configuration

### Configuration

<!-- AUTOGENERATED CONFIG DESCRIPTIONS -->

### See Also

* [npm help config](/commands/npm-config)
Loading
Loading