preface

Nodejs portal series:

  • The NodeJS module mechanism is simple

  • NPM 7.0 source analysis (a) NPM startup

  • NPM 7.0 source code analysis (2) NPM install execution logic

Front has analyzed the NPM how to start, the start of the core is to get the complete runtime configuration information, this process is important for all of the command execution, if don’t know the NPM how to parse command line parameters to the runtime configuration, first look at the NPM source code analysis (a), can go over from the portal in the past, This also helps to analyze other NPM commands.

In NPM startup logic analysis, we know that all commands in NPM 7.0 are exported as follows, and can be easily found in the lib directory with the command name.

const cmd = (args, cb) => command(args).then(() => cb()).catch(cb)
Copy the code

Here I use this command variable instead of specific NPM commands such as install, config, and so on.

NPM config –usage –help/-u/-h: NPM config –usage –help/-u/-h: NPM config –usage –help/-u/-h I’ll write another article to analyze it.

NPM Config: NPM config: NPM config: NPM Config: NPM Config: NPM Config: NPM Config: NPM Config

npm config set <key>=<value> [<key>=<value> ...]  npm config get [<key> [<key> ...]] npm config delete <key> [<key> ...]  npm config list [--json] npm config edit npm set <key>=<value> [<key>=<value> ...]  npm get [<key> [<key> ...]] alias: cCopy the code

Argument parsing

Before introducing the NPM config subcommand, let’s find config.js in the NPM source directory lib and find the config method.

const config = async ([action, ...args]) => {  
    npm.log.disableProgress()  
    try {    
        switch (action) {      
            case 'set':        
                await set(args)        
                break      
            case 'get':        
                await get(args)        
                break      
            case 'delete':      
            case 'rm':      
            case 'del':        
                await del(args)        
                break      
            case 'list':      
            case 'ls':        
                await (npm.flatOptions.json ? listJson() : list())        
                break      
            case 'edit':        
                await edit()        
                break      
            default:        
                throw UsageError()    
        }  
    } finally {    
        npm.log.enableProgress()  
    }
}
Copy the code

The action arguments received here are the names of all the subcommands available to NPM config. Args is the KV pair of all configuration items.

NPM config allows you to add, delete, change, and check run-time configuration items.

Set

From the logical branch of config method, we find that the set process is mainly in the set method, which processes all configuration items KV pair.

Assume that we run NPM config set registry=https://registry.npmjs.org/ to set the registry, this is our most often used, it should be functioning.

We can see from the picture there is just one kv to registry=https://registry.npmjs.org/ args

const set = async (args) => { if (! args.length) throw UsageError() const where = npm.flatOptions.global ? 'global' : 'user' for (const [key, val] of Object.entries(keyValues(args))) { npm.log.info('config', 'set %j %j', key, val) npm.config.set(key, val || '', where) if (! npm.config.validate(where)) npm.log.warn('config', 'omitting invalid config values') } await npm.config.save(where) }Copy the code

From the set method source code, can according to the original in the args kv to obtain real kv map, the keyValues for registry=https://registry.npmjs.org/ parsed (args)

And by the way,

const keyValues = args => {  
    const kv = {}  
    for (let i = 0; i < args.length; i++) {    
        const arg = args[i].split('=')    
        const key = arg.shift()    
        const val = arg.length ? arg.join('=')      
            : i < args.length - 1 ? args[++i]      
            : ''    
        kv[key.trim()] = val.trim()  
    }  
    return kv
}
Copy the code

[key, value, k2=v2, k3, v3…] {key: value, k2: v2, k3: v3}} {key: value, k2: v2, k3: v3}} {key: value, k2: v2, k3: v3}} {key: value, k2: v2, k3: v3}} {key: value, k2: v2, k3: v3}} {key: value, k2: v2, k3: v3}}

Once we have the real KV map, we need to traverse it:

 npm.config.set(key, val || '', where)
Copy the code

Where is controlled by -g to determine whether it is global or user. There is a prototype chain of configuration item data in the NPM runtime configuration item Config instance. This where is actually the runtime configuration namespace, which determines the effective scope of the configuration item

Call npm.config.set one by one to set it up

set (key, val, where = 'cli') {    
    ...

    this.data.get(where).data[key] = val    
    // this is now dirty, the next call to this.valid will have to check it    
    this.data.get(where)[_valid] = null  
}
Copy the code

Finally, save the configuration items.

await npm.config.save(where)
Copy the code

The Config instance’s save method is central to the setup process:

You must combine the source code below

  • When settinguserWhen configuring a namespace item, the current item is updated firstAuthentication configuration
  • createThe source directory.mkdirp(dir)To generate the.npmrcFile,writeFile(conf.source, iniData, 'utf8'), because when NPM starts, it also reads from the corresponding namespace.npmrcFile. (Combined with the figure belowconfInformation)

Check the source code below

async save (where) { ... // upgrade auth configs to more secure variants before saving if (where === 'user') { const reg = this.get('registry') const creds = this.getCredentialsByURI(reg) // we ignore this error because the failed set already removed // anything that might be a security hazard, and it won't be // saved back to the .npmrc file, so we're good. try { this.setCredentialsByURI(reg, creds) } catch (_) {} } const iniData = ini.stringify(conf.data).trim() + '\n' if (! iniData.trim()) { // ignore the unlink error (eg, if file doesn't exist) await unlink(conf.source).catch(er => {}) return } const dir = dirname(conf.source) await // create directory await writeFile(conf.source, iniData, NPMRC file // don't leave a root-owned config file lying around if (myUid === 0) {const st = await stat(dir).catch(() => null) if (st && (st.uid ! == myUid || st.gid ! == myGid)) await chown(conf.source, st.uid, st.gid).catch(() => {}) } const mode = where === 'user' ? 0o600 : 0o666 await chmod(conf.source, mode) }Copy the code

At this point, the execution of NPM config set is complete. The run-time configuration items are basically set up around the Config instance of the NPM instance.

As a bonus, I don’t know if you noticed that there is an NPM set in the NPM config help. NPM set as a command of NPM, which is essentially NPM config set, NPM set can be used as a shortcut to NPM config set.

Get

The Get subcommand is much simpler than the Set subcommand. Its purpose is to show the corresponding configuration item or all configuration items.

Using registry as an example, this time we get the value of the registry configuration item, NPM config get Registry.

const get = async keys => { if (! keys.length) return list() const out = [] for (const key of keys) { if (! publicVar(key)) throw `The ${key} option is protected, and cannot be retrieved in this way` const pref = keys.length > 1 ? `${key}=` : '' out.push(pref + npm.config.get(key)) } output(out.join('\n')) }Copy the code

NPM config get fallback NPM config list fallback NPM config get fallback NPM config get is equivalent to NPM config list. The list method is the same as the NPM config get method.

Here keys is [“registry”]. If you want to obtain configuration items corresponding to multiple keys, the corresponding key value is displayed here.

The internal logic of get method is very simple. It is to traverse keys, obtain the corresponding value from Config instance, splice it into out output string, and finally output to terminal.

The Config instance holds the prototype chain of configuration data, which is searched namespace-by-namespace when retrieved.

At this point, the Get subcommand is executed, and the logic is relatively simple.

Also, there is a shortcut NPM Get for the Get subcommand in the help section.

NPM config list NPM config list NPM config list NPM config Get NPM config list NPM config Get NPM config list

If NPM config list –json, it will be displayed as listJson. If NPM config list –json is used, it will be displayed as listJson. If NPM config list –json is used, it will be displayed as listJson.

Let’s take a look at listJson,

const listJson = async () => { const publicConf = {} for (const key in npm.config.list[0]) { if (! publicVar(key)) continue publicConf[key] = npm.config.get(key) } output(JSON.stringify(publicConf, null, 2)) }Copy the code

Npm.config. list stores the ConfigData prototype chain. ListJson does not have complex logic.

For those unfamiliar with the ConfigData prototype chain, go to the portal

Let’s look at the normal list display:

Paste the source code directly, from the source code, ordinary list has several special processing

  • Special command line option flag bit--long, which controls whether the default configuration items are displayed.
  • All configuration key items are sorted
  • The meetingkey=valueIs displayed line by line, as opposed tolistJsontheJson stringIn the form of show

Refer to the following source code

const list = async () => { const msg = [] const { long } = npm.flatOptions for (const [where, { data, source }] of npm.config.data.entries()) { if (where === 'default' && ! long) continue const keys = Object.keys(data).sort((a, b) => a.localeCompare(b)) if (! keys.length) continue msg.push(`; "${where}" config from ${source}`, '') for (const k of keys) { const v = publicVar(k) ? JSON.stringify(data[k]) : '(protected)' const src = npm.config.find(k) const overridden = src ! == where msg.push((overridden ? '; ' : '') + `${k} = ${v} ${overridden ? `; overridden by ${src}` : ''}`) } msg.push('') } ... output(msg.join('\n').trim()) }Copy the code

The List subcommand is similar to the Get subcommand in essence. The List subcommand displays the values of all configuration keys, and the Get subcommand displays the values of one or more configuration keys. Of course, Get can also display all keys at once.

Delete

The delete subcommand has two equivalent aliases: rm and del.

Let’s go straight to the del method:

const del = async keys => { if (! keys.length) throw UsageError() const where = npm.flatOptions.global ? 'global' : 'user' for (const key of keys) npm.config.delete(key, where) await npm.config.save(where) }Copy the code

Similar to the set method, let’s take NPM config Delete Registry as an example. [“registry”]

Again, you need to traverse keys and delete keys one by one. Again, the configuration item’s namespace has the option of -g.

npm.config.delete(key, where)
Copy the code

The NPM Config command and its subcommands are about the Config instance of the NPM instance.

delete (key, where = 'cli') { if (! this.loaded) throw new Error('call config.load() before deleting values') if (! confTypes.has(where)) throw new Error('invalid config location param: ' + where) delete this.data.get(where).data[key] }Copy the code

The core logic of npm.config.delete is to delete the corresponding key from the corresponding namespace

delete this.data.get(where).data[key]
Copy the code

Last but not least, save the current configuration, as with the Set subcommand.

 await npm.config.save(where)
Copy the code

This completes the logic for executing the Delete subcommand.

conclusion

NPM config needs to be used flexibly in daily development. Depending on it, we can use some different NPM runtime, such as setting up the default installation of Prefix, Registry; Set the user information when the package initializes init and so on. NPM 7.0 code refactoring, to a certain extent, the importance of the NPM instance runtime configuration Config function more prominent, and the NPM runtime core more cohesive, for the extension and maintenance of NPM command provides a solid foundation.