Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1461b52
Install `@npmcli/config`
confused-Techie Mar 11, 2026
9635616
Migrate `config` to use `@npmcli/config`
confused-Techie Mar 11, 2026
5c75867
`@npmcli/config` doesn't want to install on older node
confused-Techie Mar 11, 2026
585653a
Move `@npmcli/config` to `apm.getNpmConfig()`
confused-Techie Mar 14, 2026
2cf9624
Remove no-longer used dep in `config.js`
confused-Techie Mar 14, 2026
d739fa9
Make `git.js` use `apm.getNpmConfig()`
confused-Techie Mar 14, 2026
07365a7
Whoops, module is still used
confused-Techie Mar 14, 2026
f107cfe
Stop logging `silly` logs by default
confused-Techie Mar 14, 2026
cee7d14
Make `apm-cli.js` use `apm.getNpmConfig()`
confused-Techie Mar 14, 2026
12c1582
Update `apm.getSetting()`
confused-Techie Mar 14, 2026
52ed0ad
Use `apm.getSetting()` in `apm-cli.js`
confused-Techie Mar 14, 2026
64ac4bb
Use `apm.getSetting()` in `git.js`
confused-Techie Mar 14, 2026
ac55832
Make sure to save value deletion in `config.js`
confused-Techie Mar 14, 2026
b15d5f6
Add `@npmcli/arborist`
confused-Techie Mar 14, 2026
5511bb7
Migrate `clean.js` to Arborist; Add logging utility for Arborist
confused-Techie Mar 15, 2026
7651799
Migrate `request.js` to `apm.getNpmConfig()`
confused-Techie Mar 15, 2026
453137e
Migrate `dedupe.js` to Arborist
confused-Techie Mar 15, 2026
cb4234d
Get `ci.js` ALMOST working
confused-Techie Mar 15, 2026
d919504
Revert "Get `ci.js` ALMOST working"
confused-Techie Mar 15, 2026
1010aa5
Merge branch 'master' into remove-npm-copy
confused-Techie Mar 15, 2026
12fd71c
`yarn install` to cleanup lockfile
confused-Techie Mar 15, 2026
5b57598
Add `npm-package-arg`
confused-Techie Mar 15, 2026
f6f5e7e
Add `apm.getArboristConfig()` to reduce duplication
confused-Techie Mar 15, 2026
b91b9a0
Migrate `rebuild` to `Arborist`
confused-Techie Mar 15, 2026
f076e47
Use `apm` directly for now deleted variable `this.atomDirectory` in `…
confused-Techie Mar 15, 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
2 changes: 1 addition & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ jobs:

- name: Install dependencies
if: ${{ matrix.node_version != 14 }}
run: yarn install
run: yarn install --ignore-engines

- name: Install dependencies (NodeJS v14)
# node-gyp v10+'s `engines` field dropped support for Node 14... Ignore and install anyway.
Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
"test": "npm run check-version && jasmine --config=./spec/jasmine.json"
},
"dependencies": {
"@npmcli/arborist": "^9.4.1",
"@npmcli/config": "^10.7.1",
"asar-require": "0.3.0",
"async": "^3.2.0",
"colors": "~1.4.0",
Expand All @@ -30,6 +32,7 @@
"hosted-git-info": "^3.0.7",
"keytar": "^7.7.0",
"npm": "https://github.com/pulsar-edit/npm-cli/releases/download/v6.14.19-pulsar2/npm-6.14.19-pulsar2.tgz",
"npm-package-arg": "^13.0.2",
"open": "7.3.0",
"plist": "3",
"read": "^3",
Expand All @@ -40,6 +43,7 @@
"superagent-proxy": "^3.0.0",
"tar": "^6.0.5",
"temp": "^0.9.4",
"treeverse": "^3.0.0",
"underscore-plus": "1.x",
"wordwrap": "1.0.0",
"yargs": "^3.32.0"
Expand Down
8 changes: 4 additions & 4 deletions spec/config-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ describe('apm config', () => {
silenceOutput();
const atomHome = temp.mkdirSync('apm-home-dir-');
process.env.ATOM_HOME = atomHome;
userConfigPath = path.join(atomHome, '.apmrc');
userConfigPath = path.join(atomHome, '.apm', '.apmrc');
// Make sure the cache used is the one for the test env
delete process.env.npm_config_cache;
});
Expand All @@ -19,7 +19,7 @@ describe('apm config', () => {
it('reads the value from the global config when there is no user config', async () => {
await apmRun(['config', 'get', 'cache']);
expect(
process.stdout.write.calls.argsFor(0)[0].trim()
console.log.calls.mostRecent().args[0]
).toBe(path.join(process.env.ATOM_HOME, '.apm'));
});
});
Expand All @@ -31,8 +31,8 @@ describe('apm config', () => {
expect(fs.isFileSync(userConfigPath)).toBe(true);
await apmRun(['config', 'get', 'foo']);
expect(
process.stdout.write.calls.argsFor(0)[0].trim()
).toBe('bar');
console.log.calls.mostRecent().args[0]
).toBe("bar");
});
});
});
40 changes: 20 additions & 20 deletions src/apm-cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,38 +166,38 @@ async function getAtomVersion() {
}

function getPythonVersion() {
return new Promise((resolve, _reject) => {
const npmOptions = {
userconfig: config.getUserConfigPath(),
globalconfig: config.getGlobalConfigPath()
};
npm.load(npmOptions, () => {
let python = npm.config.get("python") ?? process.env.PYTHON;
return new Promise(async (resolve, reject) => {
try {
let pythonNpmConf = await config.getSetting("python");
let python = pythonNpmConf ?? process.env.PYTHON;
if (config.isWin32() && !python) {
let rootDir = process.env.SystemDrive ??= 'C:\\';
if (rootDir[rootDir.length - 1] !== '\\') { rootDir += '\\'; }
const pythonExe = path.resolve(rootDir, 'Python27', 'python.exe');
let rootDir = process.env.SystemDrive ??= "C:\\";
if (rootDir[rootDir.length -1] !== "\\") { rootDir += "\\"; }
const pythonExe = path.resolve(rootDir, "Python27", "python.exe");
if (fs.isFileSync(pythonExe)) { python = pythonExe; }
}

python ??= 'python';
python ??= "python";

const spawned = spawn(python, ['--version']);
const spawned = spawn(python, ["--version"]);
const outputChunks = [];
spawned.stderr.on('data', chunk => outputChunks.push(chunk));
spawned.stdout.on('data', chunk => outputChunks.push(chunk));
spawned.on('error', () => {});
return spawned.on('close', code => {
spawned.stderr.on("data", chunk => outputChunks.push(chunk));
spawned.stdout.on("data", chunk => outputChunks.push(chunk));
spawned.on("error", () => {});
return spawned.on("close", code => {
let version, name;
if (code === 0) {
[name, version] = Buffer.concat(outputChunks).toString().split(' ');
[name, version] = Buffer.concat(outputChunks).toString().split(" ");
version = version?.trim();
}
return resolve(version);
});
});
})
} catch(err) {
console.error(err);
reject(err);
}
});
};
}

module.exports = {
run(args, callback) {
Expand Down
77 changes: 75 additions & 2 deletions src/apm.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ const child_process = require('child_process');
const fs = require('./fs');
const path = require('path');
const npm = require('npm');
const npmConfig = require("@npmcli/config");
const npmDefs = require("@npmcli/config/lib/definitions");
let asarPath = null;

module.exports = {
Expand Down Expand Up @@ -161,8 +163,79 @@ module.exports = {
},

async getSetting(key) {
await this.loadNpm();
return npm.config.get(key);
const conf = await this.getNpmConfig();
return conf.get(key);
},

async getNpmConfig() {
const configArgs =
[
"--globalconfig", this.getGlobalConfigPath(),
"--userconfig", this.getUserConfigPath()
];

const conf = new npmConfig({
npmPath: "",
definitions: {
...npmDefs.definitions,
// Define any default overrides
cache: { ...npmDefs.definitions.cache, default: this.getCacheDirectory() }
},
shorthands: npmDefs.shorthands,
flatten: npmDefs.flatten,
argv: configArgs,
env: {
// TODO: Do we need to include anything else from the env?
// previously used `...process.env` but it lead to lots of useless warnings.
HOME: path.join(this.getAtomDirectory(), ".node-gyp"),
RUSTUP_HOME: this.getRustupHomeDirPath()
}
});

// re-route process object log events to console
process.on("log", (level, ...args) => {
if (level !== "silly") {
console.log(level, ...args);
}
});

return new Promise((resolve, reject) => {
try {
conf.load().then(() => {
conf.validate();
resolve(conf);
}).catch((err) => {
reject(err);
});
} catch(err) {
reject(err);
}
});
},

getArboristConfig() {
// Provides the options that should be passed to arborist on creation.
// Provided the defaults, with our default overrides from `getNpmConfig()`
// As well as some special options for arborist in our environment
return new Promise(async (resolve, reject) => {
try {
const npmConf = await this.getNpmConfig();
const { flat } = npmConf;

// Values set in NPM: https://github.com/npm/cli/blob/v11.11.1/lib/npm.js
flat.nodeVersion = process.version;

// Values for arborist
flat.registry = process.env.npm_config_registry ?? "https://registry.npmjs.org";
flat.cpu = this.getElectronArch();
flat.nodeGyp = path.join(this.getAtomDirectory(), ".node-gyp");

resolve(flat);
} catch(err) {
console.error(err);
reject(err);
}
});
},

setupApmRcFile() {
Expand Down
31 changes: 22 additions & 9 deletions src/clean.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

const { performance } = require("node:perf_hooks");
const yargs = require('yargs');

const Arborist = require("@npmcli/arborist");
const Command = require('./command');

module.exports =
Expand All @@ -9,7 +9,6 @@ class Clean extends Command {

constructor() {
super();
this.atomNpmPath = require.resolve('npm/bin/npm-cli');
}

parseOptions(argv) {
Expand All @@ -26,11 +25,25 @@ as a dependency in the package.json file.\
}

run(_options) {
process.stdout.write("Removing extraneous modules ");
return new Promise((resolve, reject) =>
void this.fork(this.atomNpmPath, ['prune'], (...args) =>
void this.logCommandResults(...args).then(resolve, reject)
)
);
process.stdout.write("Removing extraneous modules\n");

// Process here is modeled after the NPM CLI v11.11.1
// https://github.com/npm/cli/blob/v11.11.1/lib/commands/prune.js
const started = performance.now();
const arb = new Arborist({
registry: process.env.npm_config_registry ?? "https://registry.npmjs.org"
});
return new Promise(async (resolve, reject) => {
try {
await arb.prune();
this.logArboristResults(arb, started);
this.logSuccess();
resolve();
} catch(err) {
console.error(err);
this.logFailure();
reject(err);
}
});
}
};
93 changes: 93 additions & 0 deletions src/command.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@

const child_process = require('child_process');
const { performance } = require("node:perf_hooks");
const path = require('path');
const semver = require('semver');
const { depth } = require("treeverse");
const config = require('./apm');
const git = require('./git');

Expand Down Expand Up @@ -88,6 +90,97 @@ class Command {
}
}

logArboristResults(arb, startTime) {
// Logging of Arborist data modeled after NPM v11.11.1
// https://github.com/npm/cli/blob/v11.11.1/lib/utils/reify-output.js
const { diff, actualTree } = arb;

const summary = {
add: [],
added: 0,
change: [],
changed: 0,
remove: [],
removed: 0
};

if (diff) {
depth({
tree: diff,
visit: d => {
switch(d.action) {
case "REMOVE":
summary.removed++;
summary.remove.push({
name: d.actual.name,
version: d.actual.package.version,
path: d.actual.path
});
break;
case "ADD":
if (actualTree.inventory.has(d.ideal)) {
summary.added++;
summary.add.push({
name: d.ideal.name,
version: d.ideal.package.version,
path: d.ideal.path
});
}
break;
case "CHANGE":
summary.changed++;
summary.change.push({
from: {
name: d.actual.name,
version: d.actual.package.version,
path: d.actual.path
},
to: {
name: d.ideal.name,
version: d.ideal.package.version,
path: d.ideal.path
}
});
break;
default:
return;
}
},
getChildren: d => d.children
})
}

// Print out the message
const msg = ["\n"];
if (summary.added === 0 && summary.removed === 0 && summary.changed === 0) {
msg.push("up to date");
} else {
if (summary.added) {
msg.push(`added ${summary.added} package${summary.added === 1 ? "" : "s"}`);
}

if (summary.removed) {
if (summary.added) {
msg.push(", ");
}
msg.push(`removed ${summary.removed} package${summary.removed === 1 ? "" : "s"}`);
}

if (summary.changed) {
if (summary.added || summary.removed) {
msg.push(", ");
}
msg.push(`changed ${summary.changed} package${summary.changed === 1 ? "" : "s"}`);
}
}

if (startTime) {
msg.push(` in ${(performance.now() - startTime).toFixed(2)}ms`);
}

console.log(msg.join(""));
}

normalizeVersion(version) {
if (typeof version === 'string') {
// Remove commit SHA suffix
Expand Down
Loading
Loading