Skip to content
This repository has been archived by the owner on Nov 8, 2024. It is now read-only.

Commit

Permalink
chore: fix dependencies bundling for yarn workspaces
Browse files Browse the repository at this point in the history
This improves the hack which gets rid of protagonist (i.e. C++ compilation
of drafter, the API Blueprint parser). When we moved dredd-transactions
to the monorepo, where dependencies are managed by yarn workspaces,
the hack to get rid of protagonist stopped to work. The hack relies on
'bundledDependencies', but those stopped to work. 'npm pack' cannot find
the dependencies to bundle if they're in the root 'node_modules' directory.
'yarn pack' doesn't work either, it has bugs and it doesn't support
'bundledDependencies' or the workspaces very well - see yarnpkg/yarn#6794

This change overcomes the limitation by improving the prepack.js script.
It now iterates over the 'bundledDependencies' and symlinks them to the
local 'node_modules' directory where 'npm pack' can pick them up. After
packing is done, there's a new postpack.js script, which makes sure the
symlinks get cleaned up. The resulting .tgz then contains the dependencies
bundled - without protagonist.
  • Loading branch information
honzajavorek committed Oct 18, 2019
1 parent 66d6a54 commit f341455
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 6 deletions.
1 change: 1 addition & 0 deletions packages/dredd-transactions/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
dredd-transactions-*.tgz
prepack-symlinks.log
/test/fixtures/**/*.json
1 change: 1 addition & 0 deletions packages/dredd-transactions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
},
"scripts": {
"prepack": "node ./scripts/prepack.js",
"postpack": "node ./scripts/postpack.js",
"build": "exit 0",
"clean": "rimraf ./coverage",
"lint": "eslint --ignore-path .gitignore .",
Expand Down
20 changes: 20 additions & 0 deletions packages/dredd-transactions/scripts/postpack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env node
// Cleans up after the prepack.js script

const fs = require('fs');
const path = require('path');

const PACKAGE_DIR = path.resolve(path.dirname(__filename), '..');
const SYMLINKS_LOG = path.join(PACKAGE_DIR, 'prepack-symlinks.log');


if (fs.existsSync(SYMLINKS_LOG)) {
fs.readFileSync(SYMLINKS_LOG, 'utf8')
.split('\n')
.filter(line => line.trim())
.map(dependencyName => path.join(PACKAGE_DIR, 'node_modules', dependencyName))
.filter(symlinkPath => fs.existsSync(symlinkPath))
.forEach(symlinkPath => fs.unlinkSync(symlinkPath));

fs.unlinkSync(SYMLINKS_LOG);
}
58 changes: 52 additions & 6 deletions packages/dredd-transactions/scripts/prepack.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,60 @@

const fs = require('fs');
const path = require('path');
const rimraf = require('rimraf');

const drafterPackageData = require('../../../node_modules/drafter/package');
const PACKAGE_DIR = path.resolve(path.dirname(__filename), '..');
const SYMLINKS_LOG = path.join(PACKAGE_DIR, 'prepack-symlinks.log');
const DRAFTER_PATH = path.join(PACKAGE_DIR, 'node_modules', 'drafter');


function readPackageJson(packageDir) {
return JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json')));
}

function writePackageJson(packageDir, packageData) {
fs.writeFileSync(path.join(packageDir, 'package.json'), JSON.stringify(packageData, null, 2));
}

/**
* Goes through the whole dependency tree of a given top-level dependency
* and makes sure all packages involved are accessible in the local
* 'node_modules' directory at least in form of symlinks.
*
* This is useful if the project uses yarn workspaces, but wants to publish
* the package with npm and uses 'bundledDependencies'. 'npm pack' is not able
* to find packages if they're put into the root 'node_modules' by yarn and
* wouldn't be able to bundle them.
*
* @param {string} dependencyName Dependency tree root
*/
function symlinkDependencyTreeToLocalNodeModules(dependencyName) {
const localDependencyPath = path.join(PACKAGE_DIR, 'node_modules', dependencyName);
if (!fs.existsSync(localDependencyPath)) {
fs.symlinkSync(`../../../node_modules/${dependencyName}`, localDependencyPath);
fs.appendFileSync(SYMLINKS_LOG, `${dependencyName}\n`);
}
const packageData = readPackageJson(localDependencyPath);
const dependencies = Object.keys(packageData.dependencies || {});
dependencies.forEach(symlinkDependencyTreeToLocalNodeModules);
}


// make sure all bundled deps are accessible in the local 'node_modules' dir
const packageData = readPackageJson(PACKAGE_DIR, 'package.json');
const { bundledDependencies } = packageData;
bundledDependencies.forEach(symlinkDependencyTreeToLocalNodeModules);

// alter drafter's package.json so it doesn't depend on protagonist
const drafterPackageData = readPackageJson(DRAFTER_PATH);
delete drafterPackageData.dependencies.protagonist;
delete drafterPackageData.optionalDependencies.protagonist;
const json = JSON.stringify(drafterPackageData, null, 2);

const drafterPackageJsonPath = path.resolve(path.dirname(__filename), '../../../', 'node_modules/drafter/package.json')
writePackageJson(DRAFTER_PATH, drafterPackageData);

// prettier-ignore
fs.writeFileSync(drafterPackageJsonPath, json);
// get rid of protagonist everywhere
[
path.join(PACKAGE_DIR, 'node_modules', 'protagonist'),
path.join(PACKAGE_DIR, '..', '..', 'node_modules', 'protagonist'),
]
.filter(protagonistPath => fs.existsSync(protagonistPath))
.forEach(protagonistPath => rimraf.sync(protagonistPath));

0 comments on commit f341455

Please sign in to comment.