Skip to content

Commit

Permalink
Initial implementation, tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ljharb committed Jan 8, 2022
1 parent 24a4209 commit b52e661
Show file tree
Hide file tree
Showing 10 changed files with 306 additions and 2 deletions.
11 changes: 11 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"root": true,

"extends": "@ljharb/eslint-config/node/12",

"rules": {
"func-style": 0,
"object-curly-newline": 0,
"sort-keys": 0,
},
}
12 changes: 12 additions & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# These are supported funding model platforms

github: [ljharb]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: npm/get-dep-tree
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
7 changes: 7 additions & 0 deletions .github/workflows/node-pretest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name: 'Tests: pretest/posttest'

on: [pull_request, push]

jobs:
tests:
uses: ljharb/actions/.github/workflows/pretest.yml@main
18 changes: 18 additions & 0 deletions .github/workflows/node.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: 'Tests: node.js'

on: [pull_request, push]

jobs:
tests:
uses: ljharb/actions/.github/workflows/node.yml@main
with:
range: '>= 16 || ^14.15 || ^12.13'
type: minors
command: npm run tests-only

node:
name: 'node'
needs: [tests]
runs-on: ubuntu-latest
steps:
- run: 'echo tests completed'
15 changes: 15 additions & 0 deletions .github/workflows/rebase.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: Automatic Rebase

on: [pull_request_target]

jobs:
_:
name: "Automatic Rebase"

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: ljharb/rebase@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12 changes: 12 additions & 0 deletions .github/workflows/require-allow-edits.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: Require “Allow Edits”

on: [pull_request_target]

jobs:
_:
name: "Require “Allow Edits”"

runs-on: ubuntu-latest

steps:
- uses: ljharb/require-allow-edits@main
9 changes: 9 additions & 0 deletions .nycrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"all": true,
"check-coverage": false,
"reporter": ["text-summary", "text", "html", "json"],
"exclude": [
"coverage",
"test"
]
}
105 changes: 105 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
'use strict';

const Arborist = require('@npmcli/arborist');
const colors = require('colors/safe');
const { manifest } = require('pacote');
const lockfileInfo = require('lockfile-info');

function prune(tree, {
dev: keepDev,
production: keepProduction,
peer: keepPeer,
}) {
if (!keepDev || !keepProduction) {
for (const node of tree.children.values()) {
if ((!keepDev && node.dev) || (!keepProduction && !node.dev) || (!keepPeer && node.peer)) {
node.root = null;
}
}
}
return tree;
}

async function getBaseTree({
mode,
arb,
fullMetadata,
packumentCache,
logger,
}) {
const {
hasNodeModulesDir,
hasLockfile,
hasPackageJSON,
lockfileVersion,
} = await lockfileInfo();

if (mode === 'actual' || (mode === 'auto' && hasNodeModulesDir)) {
const messages = [].concat(
hasNodeModulesDir ? `\`${colors.gray('node_modules')}\` found` : [],
mode === 'actual' ? 'mode is “actual”' : [],
);
logger(colors.green(`${messages.join(', ')}; loading tree from disk...`));
return arb.loadActual({ fullMetadata: true, packumentCache });
}

if (mode === 'virtual' || (mode === 'auto' && hasLockfile)) {
if (hasLockfile && lockfileVersion < 2) {
const messages = ['v1 lockfile found'].concat(mode === 'virtual' ? 'mode is “virtual”' : []);
logger(colors.green(`${messages.join(', ')}; loading ideal tree from lockfile...`));
const tree = await arb.buildIdealTree({ fullMetadata: true });
await Promise.all(Array.from(
tree.children.values(),
async (node) => {
// eslint-disable-next-line no-param-reassign
node.package = await manifest(`${node.name}@${node.package.version}`, { fullMetadata: true, packumentCache });
},
));
return tree;
}
const messages = [].concat(
hasLockfile ? 'Lockfile found' : [],
mode === 'virtual' ? 'mode is “virtual”' : [],
);
logger(colors.green(`${messages.join(', ')}; loading virtual tree from lockfile...`));
return arb.loadVirtual({ fullMetadata: true, packumentCache });
}

const messages = [].concat(
`\`${colors.gray('package.json')}\` ${hasPackageJSON ? '' : 'not '}found`,
mode === 'ideal' ? 'mode is “ideal”' : [],
);
logger(colors.green(`${messages.join(', ')}; building ideal tree from \`${colors.gray('package.json')}\`...`));
return arb.buildIdealTree({ fullMetadata, packumentCache, update: true });
}

const defaultLogger = (x) => console.log(x);

module.exports = async function getTree(mode, {
dev = false,
peer = true,
production = true,
fullMetadata = false,
packumentCache = new Map(),
path = process.cwd(),
logger = defaultLogger,
} = {}) {
const arb = new Arborist({
fullMetadata,
packumentCache,
path,
});
const tree = await getBaseTree({
mode,
arb,
fullMetadata,
packumentCache,
logger,
});
prune(tree, {
dev,
production,
peer,
});
return tree;
};
28 changes: 26 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
"./package.json": "./package.json"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"lint": "eslint --ext=js,mjs .",
"pretest": "npm run lint",
"tests-only": "nyc tape 'test/**/*.js'",
"test": "npm run tests-only",
"posttest": "aud --production"
},
"repository": {
"type": "git",
Expand All @@ -25,9 +29,29 @@
"arborist"
],
"author": "Jordan Harband <ljharb@gmail.com>",
"funding": {
"url": "https://github.com/sponsors/ljharb"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/ljharb/get-dep-tree/issues"
},
"homepage": "https://github.com/ljharb/get-dep-tree#readme"
"homepage": "https://github.com/ljharb/get-dep-tree#readme",
"dependencies": {
"@npmcli/arborist": "^4.2.0",
"colors": "^1.4.0",
"lockfile-info": "^1.0.0",
"pacote": "^12.0.2"
},
"engines": {
"node": ">= 16 || ^14.15 || ^12.13"
},
"devDependencies": {
"@ljharb/eslint-config": "^20.1.0",
"aud": "^1.1.5",
"eslint": "^8.6.0",
"nyc": "^15.1.0",
"sinon-sandbox": "^2.0.6",
"tape": "^5.4.0"
}
}
91 changes: 91 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
'use strict';

const test = require('tape');
const sinon = require('sinon-sandbox');
const { Node } = require('@npmcli/arborist');

const getTree = require('..');
const { stripColors } = require('colors/safe');

test('getTree: auto', async (t) => {
t.teardown(() => sinon.restore());

const logger = sinon.stub();
const p = getTree('auto', { logger });
t.equal(Promise.resolve(p), p, 'returns a Promise');

const tree = await p;

t.deepEqual(
logger.getCalls().map((x) => x.args.map((arg) => stripColors(arg))),
[
['`node_modules` found; loading tree from disk...'],
],
'expected messages were logged',
);

t.ok(tree instanceof Node);
});

test('getTree: actual', async (t) => {
t.teardown(() => sinon.restore());

const logger = sinon.stub();
const p = getTree('actual', { logger });
t.equal(Promise.resolve(p), p, 'returns a Promise');

const tree = await p;

t.deepEqual(
logger.getCalls().map((x) => x.args.map((arg) => stripColors(arg))),
[
['`node_modules` found, mode is “actual”; loading tree from disk...'],
],
'expected messages were logged',
);

t.ok(tree instanceof Node);
});

test('getTree: virtual', async (t) => {
t.teardown(() => sinon.restore());

const logger = sinon.stub();
const p = getTree('virtual', { logger });
t.equal(Promise.resolve(p), p, 'returns a Promise');

await p.then(null, (e) => {
t.ok(e instanceof Error, 'is an Error');
t.equal(e.message, 'loadVirtual requires existing shrinkwrap file');
});

t.deepEqual(
logger.getCalls().map((x) => x.args.map((arg) => stripColors(arg))),
[
['mode is “virtual”; loading virtual tree from lockfile...'],
],
'expected messages were logged',
);
});

test('getTree: ideal', async (t) => {
t.teardown(() => sinon.restore());

const logger = sinon.stub();
const p = getTree('ideal', { logger });
t.equal(Promise.resolve(p), p, 'returns a Promise');

const tree = await p;

t.deepEqual(
logger.getCalls().map((x) => x.args.map((arg) => stripColors(arg))),
[
['`package.json` found, mode is “ideal”; building ideal tree from `package.json`...'],
],
'expected messages were logged',
);

t.ok(tree instanceof Node);
});

// TODO: fixture tests, to cover all scenarios with all modes, and check an actual tree

0 comments on commit b52e661

Please sign in to comment.