Skip to content

Commit

Permalink
Added install:global command
Browse files Browse the repository at this point in the history
  • Loading branch information
josephjclark committed Apr 20, 2023
1 parent 52f766f commit 00bdd5b
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 109 deletions.
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,17 @@ pnpm changeset publish --otp <OTP>
git push --follow-tags
```

## Testing the built package
## Testing the CLI on a branch

From the repo root, run `pnpm install:global`.

This will build the CLI into `./dist`, set the version to the current branch name, and install it globally as `openfnx`.

Run `openfnx` to use this dev-version of the CLI without overriding your production install.

This uses a similar technique to the release CLI below.

## Testing the release CLI

You can test the built CLI package to ensure it works before publishing it.

Expand Down
44 changes: 44 additions & 0 deletions build/install-global.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const { exec } = require('node:child_process');
const path = require('node:path');
const {
findPackages,
mapPackages,
ensureOutputPath,
updatePkg,
getLocalTarballName,
} = require('./pack-helpers');

const outputPath = process.argv[2] || './dist';

// TODO need some kinda of special suffix for all these

// Package everything up like a local build
exec('git branch --show-current', {}, async (err, branchName) => {
const files = await findPackages();
const pkgs = mapPackages(files);
await ensureOutputPath(outputPath);

// Intercept package.json before it's written to the tgz and override some stuff
// This won't change the tarball name though
const onPackage = (packageName, pkg) => {
if (packageName == '@openfn/cli') {
pkg.bin = {
openfnx: 'dist/index.js',
};
pkg.version = `branch/${branchName.trim()}`;
}
};

Promise.all(
files.map((f) => {
return updatePkg(pkgs, f, false, outputPath, onPackage);
})
).then(async () => {
const cliPath = getLocalTarballName(pkgs['@openfn/cli']);
const command = `npm install -g ${path.resolve(outputPath, cliPath)}`;
console.log(command);

await exec(command);
// install the local CLI globally
});
});
114 changes: 114 additions & 0 deletions build/pack-helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/**
* This will load each of the built .tgzs, extract them in-memory,
* update @openfn dependencies in package.json to use absolute local paths,
* And write back to disk.
*/
const path = require('node:path');
const fs = require('node:fs');
const { mkdir } = require('node:fs/promises');
const { createGzip } = require('node:zlib');
const tarStream = require('tar-stream');
const gunzip = require('gunzip-maybe');

// Find built packages in the ./dist folder
async function findPackages() {
return new Promise((resolve) => {
fs.readdir(path.resolve('./dist'), { encoding: 'utf8' }, (err, files) => {
resolve(files.filter((p) => !/(-local)/.test(p) && p.endsWith('tgz')));
});
});
}

const getLocalTarballName = (packagePath, noVersion = false) =>
noVersion
? packagePath.replace(/\-\d+.\d+.\d+.tgz/, '.tgz')
: packagePath.replace('.tgz', '-local.tgz');

function processPackageJSON(stream, packageMap, pack, noVersion, onPackage) {
return new Promise((resolve) => {
const data = [];
stream.on('data', (c) => data.push(c));
stream.on('end', () => {
const buf = Buffer.concat(data);

const pkg = JSON.parse(buf.toString('utf8'));
for (const dep in pkg.dependencies) {
if (packageMap[dep]) {
const mappedName = getLocalTarballName(packageMap[dep], noVersion);
console.log(`Mapping ${dep} to ${mappedName}`);
pkg.dependencies[dep] = mappedName;
}
}
if (onPackage) {
onPackage(pkg.name, pkg);
}
pack.entry(
{ name: 'package/package.json' },
JSON.stringify(pkg, null, 2),
resolve
);
});
});
}

function updatePkg(packageMap, filename, noVersion, outputPath, onPackage) {
const pkgPath = `dist/${filename}`;
console.log(' - Updating package', pkgPath);

// The packer contains the new (gzipped) tarball
const pack = tarStream.pack();

return new Promise((resolve) => {
// The extractor streams the old tarball
var extract = tarStream.extract();
extract.on('entry', (header, stream, next) => {
if (header.name === 'package/package.json') {
processPackageJSON(stream, packageMap, pack, noVersion, onPackage).then(
next
);
} else {
stream.pipe(pack.entry(header, next));
}
});

// Pipe to a -local file name
// Reading and writing to the same tarball seems to cause problems, funnily enough
const target = getLocalTarballName(
`${path.resolve(outputPath)}/${filename}`,
noVersion
);
const out = fs.createWriteStream(target);
// Note that we have to start piping to the output stream immediately,
// otherwise we get backpressure fails on the pack stream
pack.pipe(createGzip()).pipe(out);

fs.createReadStream(pkgPath).pipe(gunzip()).pipe(extract);

extract.on('finish', () => {
pack.finalize();
resolve();
});
});
}

// Map the tgz packages in dist to npm package names
const mapPackages = (files) => {
return files.reduce((obj, file) => {
const mapped = /openfn-(.+)-\d+\.\d+\.\d+\.tgz/.exec(file);
if (mapped && mapped[1]) {
obj[`@openfn/${mapped[1]}`] = `./${file}`;
}
return obj;
}, {});
};

const ensureOutputPath = async (outputPath) =>
mkdir(path.resolve(outputPath), { recursive: true });

module.exports = {
mapPackages,
updatePkg,
findPackages,
ensureOutputPath,
getLocalTarballName,
};
127 changes: 19 additions & 108 deletions build/pack-local.js
Original file line number Diff line number Diff line change
@@ -1,118 +1,29 @@
/**
* This will load each of the built .tgzs, extract them in-memory,
* update @openfn dependencies in package.json to use absolute local paths,
* And write back to disk.
*/
const path = require('node:path');
const fs = require('node:fs');
const { mkdir } = require('node:fs/promises');
const { createGzip } = require('node:zlib');
const tarStream = require('tar-stream');
const gunzip = require('gunzip-maybe');
const {
findPackages,
mapPackages,
ensureOutputPath,
updatePkg,
getLocalTarballName,
} = require('./pack-helpers');

const outputPath = process.argv[2] || './dist';
const noVersion = process.argv[3] === '--no-version';

console.log(`Building local packages to ${outputPath}`);

// Find built packages in the ./dist folder
async function findPackages() {
return new Promise((resolve) => {
fs.readdir(path.resolve('./dist'), { encoding: 'utf8' }, (err, files) => {
resolve(files.filter((p) => !/(-local)/.test(p) && p.endsWith('tgz')));
});
});
}

const ensureOutputPath = async () =>
mkdir(path.resolve(outputPath), { recursive: true });

const getLocalTarballName = (packagePath) =>
noVersion
? packagePath.replace(/\-\d+.\d+.\d+.tgz/, '.tgz')
: packagePath.replace('.tgz', '-local.tgz');

function processPackageJSON(stream, packageMap, pack) {
return new Promise((resolve) => {
const data = [];
stream.on('data', (c) => data.push(c));
stream.on('end', () => {
const buf = Buffer.concat(data);

const pkg = JSON.parse(buf.toString('utf8'));
for (const dep in pkg.dependencies) {
if (packageMap[dep]) {
const mappedName = getLocalTarballName(packageMap[dep]);
console.log(`Mapping ${dep} to ${mappedName}`);
pkg.dependencies[dep] = mappedName;
}
}
pack.entry(
{ name: 'package/package.json' },
JSON.stringify(pkg, null, 2),
resolve
);
});
});
}

function updatePkg(packageMap, filename) {
const pkgPath = `dist/${filename}`;
console.log(' - Updating package', pkgPath);

// The packer contains the new (gzipped) tarball
const pack = tarStream.pack();

return new Promise((resolve) => {
// The extractor streams the old tarball
var extract = tarStream.extract();
extract.on('entry', (header, stream, next) => {
if (header.name === 'package/package.json') {
processPackageJSON(stream, packageMap, pack).then(next);
} else {
stream.pipe(pack.entry(header, next));
}
});

// Pipe to a -local file name
// Reading and writing to the same tarball seems to cause problems, funnily enough
const target = getLocalTarballName(
`${path.resolve(outputPath)}/${filename}`
);
const out = fs.createWriteStream(target);
// Note that we have to start piping to the output stream immediately,
// otherwise we get backpressure fails on the pack stream
pack.pipe(createGzip()).pipe(out);

fs.createReadStream(pkgPath).pipe(gunzip()).pipe(extract);

extract.on('finish', () => {
pack.finalize();
resolve();
});
});
}

// Map the tgz packages in dist to npm package names
const mapPackages = (files) => {
return files.reduce((obj, file) => {
const mapped = /openfn-(.+)-\d+\.\d+\.\d+\.tgz/.exec(file);
if (mapped && mapped[1]) {
obj[`@openfn/${mapped[1]}`] = `./${file}`;
}
return obj;
}, {});
};

findPackages().then(async (files) => {
const pkgs = mapPackages(files);
await ensureOutputPath();
Promise.all(files.map((f) => updatePkg(pkgs, f))).then(() => {
const cliPath = getLocalTarballName(pkgs['@openfn/cli']);
console.log();
console.log('Build complete!');
console.log(`Install the CLI the command below:`);
console.log();
console.log(` npm install -g ${path.resolve(outputPath, cliPath)}`);
});
await ensureOutputPath(outputPath);

Promise.all(files.map((f) => updatePkg(pkgs, f, noVersion, outputPath))).then(
() => {
const cliPath = getLocalTarballName(pkgs['@openfn/cli'], noVersion);
console.log();
console.log('Build complete!');
console.log(`Install the CLI the command below:`);
console.log();
console.log(` npm install -g ${path.resolve(outputPath, cliPath)}`);
}
);
});
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"test:types": "pnpm -r --filter './packages/*' run test:types",
"pack": "pnpm -r run pack",
"pack:local": "pnpm run pack && node ./build/pack-local.js",
"install:global": "pnpm run pack && node ./build/install-global.js",
"export": "sh scripts/export.sh"
},
"keywords": [],
Expand Down

0 comments on commit 00bdd5b

Please sign in to comment.