Skip to content

Commit

Permalink
[teraslice-cli] Convert teraslice-cli to compile with esbuild (#3851)
Browse files Browse the repository at this point in the history
This PR makes the following changes:

- Compile teraslice-cli with `esbuild` and push to `npm` 
- This resolves issues with nested dependencies potentially breaking
teraslice-cli and allows for a consistent deliverable to `npm`
- Bumps `teraslice-cli` from `v2.8.2` to `v2.9.0`
- Adds additional testing for the bundled javascript file
- FIxes version `unknown` bug that occurs when running `-v` or
`--version` flag

#3850
  • Loading branch information
sotojn authored Dec 6, 2024
1 parent 7806a1a commit a9b09c9
Show file tree
Hide file tree
Showing 9 changed files with 189 additions and 25 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
],
"scripts": {
"prebuild": "./packages/xlucene-parser/scripts/generate-engine.js",
"build": "tsc --build",
"build": "tsc --build && yarn workspace teraslice-cli build",
"build:cleanup": "./scripts/build-cleanup.sh",
"build:doctor": "./scripts/build-doctor.sh",
"build:fix": "echo '[DEPRECATED], use yarn run build:doctor instead'",
Expand Down
2 changes: 1 addition & 1 deletion packages/teraslice-cli/bin/teraslice-cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ const dirPath = fileURLToPath(new URL('.', import.meta.url));
// this path.join is only used for pkg asset injection
path.join(dirPath, '../package.json');

import('../dist/src/command.js');
import '../dist/src/ts-cli.js';
12 changes: 12 additions & 0 deletions packages/teraslice-cli/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import esbuild from 'esbuild';

esbuild.build({
entryPoints: ['src/command.ts'],
outfile: 'dist/src/ts-cli.js', // Output file path
bundle: true,
platform: 'node',
format: 'esm', // Ensure compatibility with `import`
sourcemap: false,
inject: ['cjs-to-esm.js'],
external: ['esbuild']
}).catch(() => process.exit(1));
7 changes: 7 additions & 0 deletions packages/teraslice-cli/cjs-to-esm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { createRequire } from 'node:module';
import path from 'node:path';
import url from 'node:url';

globalThis.require = createRequire(import.meta.url);
globalThis.__filename = url.fileURLToPath(import.meta.url);
globalThis.__dirname = path.dirname(__filename);
39 changes: 20 additions & 19 deletions packages/teraslice-cli/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "teraslice-cli",
"displayName": "Teraslice CLI",
"version": "2.8.2",
"version": "2.9.0",
"description": "Command line manager for teraslice jobs, assets, and cluster references.",
"keywords": [
"teraslice"
Expand All @@ -14,7 +14,7 @@
"license": "MIT",
"author": "Terascope, LLC <[email protected]>",
"type": "module",
"main": "dist/src/command.js",
"main": "dist/src/ts-cli.js",
"typings": "dist/src/index.d.ts",
"bin": {
"earl": "bin/teraslice-cli.js",
Expand All @@ -27,29 +27,43 @@
"files": [
"bin/**/*",
"generator-templates/**/*",
"dist/src/**/*"
"dist/src/ts-cli.js",
"dist/src/helpers/esm-shims.js"
],
"scripts": {
"build": "tsc --build",
"build:watch": "yarn build --watch",
"build": "tsc --build && node build.js",
"build:watch": "tsc --build --watch",
"test": "ts-scripts test . --",
"test:debug": "ts-scripts test --debug . --",
"test:watch": "ts-scripts test --watch . --"
},
"dependencies": {
"esbuild": "~0.24.0"
},
"devDependencies": {
"@terascope/fetch-github-release": "~1.0.0",
"@terascope/types": "~1.3.1",
"@terascope/utils": "~1.4.1",
"@types/decompress": "~4.2.7",
"@types/diff": "~6.0.0",
"@types/ejs": "~3.1.5",
"@types/js-yaml": "~4.0.9",
"@types/prompts": "~2.4.9",
"@types/signale": "~1.4.7",
"@types/tmp": "~0.2.6",
"@types/yargs": "~17.0.33",
"chalk": "~5.3.0",
"cli-table3": "~0.6.4",
"decompress": "~4.2.1",
"diff": "~7.0.0",
"easy-table": "~1.2.0",
"ejs": "~3.1.10",
"esbuild": "~0.24.0",
"execa": "~9.5.1",
"fs-extra": "~11.2.0",
"globby": "~14.0.2",
"jest-fixtures": "~0.6.0",
"js-yaml": "~4.1.0",
"nock": "~13.5.6",
"pretty-bytes": "~6.1.1",
"prompts": "~2.4.2",
"signale": "~1.4.0",
Expand All @@ -58,19 +72,6 @@
"tty-table": "~4.2.3",
"yargs": "~17.7.2"
},
"devDependencies": {
"@types/decompress": "~4.2.7",
"@types/diff": "~6.0.0",
"@types/ejs": "~3.1.5",
"@types/js-yaml": "~4.0.9",
"@types/prompts": "~2.4.9",
"@types/signale": "~1.4.7",
"@types/tmp": "~0.2.6",
"@types/yargs": "~17.0.33",
"decompress": "~4.2.1",
"jest-fixtures": "~0.6.0",
"nock": "~13.5.6"
},
"engines": {
"node": ">=18.18.0",
"yarn": ">=1.22.19"
Expand Down
15 changes: 14 additions & 1 deletion packages/teraslice-cli/src/command.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import yargs from 'yargs';
import { fileURLToPath } from 'node:url';
import path from 'node:path';
import { readFileSync } from 'node:fs';
import { hideBin } from 'yargs/helpers';
import aliases from './cmds/aliases/index.js';
import assets from './cmds/assets/index.js';
Expand All @@ -9,8 +12,18 @@ import workers from './cmds/workers/index.js';
import controllers from './cmds/controllers/index.js';
import tjm from './cmds/tjm/index.js';

const yargsInstance = yargs(hideBin(process.argv));
/// Grab package.json version for yargs
const dirPath = fileURLToPath(new URL('.', import.meta.url));
const packageJsonPath = path.join(dirPath, '../../package.json');
let version: string;
try {
version = JSON.parse(readFileSync(packageJsonPath, { encoding: 'utf-8' })).version;
} catch {
version = 'unknown';
}

const yargsInstance = yargs(hideBin(process.argv));
yargsInstance.version(version);
// eslint-disable-next-line
yargsInstance.command(aliases)
.command(assets)
Expand Down
2 changes: 1 addition & 1 deletion packages/teraslice-cli/src/helpers/asset-src.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ export class AssetSrc {
reply.fatal(`Unable to resolve entry point due to error: ${err}`);
}

const injectPath = path.join(dirname, './esm-shims.js');
const injectPath = path.join(dirname, './helpers/esm-shims.js');

const result = await build({
bundle: true,
Expand Down
4 changes: 2 additions & 2 deletions packages/teraslice-cli/src/helpers/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ export function camelCase(str: string): string {
}

export function getPackage(filePath?: string): any {
let dataPath = filePath || path.join(dirname, '../..', 'package.json');
let dataPath = filePath || path.join(dirname, '../', 'package.json');
if (!fs.existsSync(dataPath)) {
dataPath = path.join(dirname, '../../../', 'package.json');
dataPath = path.join(dirname, '../../', 'package.json');
}
const file = fs.readFileSync(dataPath, 'utf8');
return JSON.parse(file);
Expand Down
131 changes: 131 additions & 0 deletions packages/teraslice-cli/test/bundled/bundled-cli-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { execFile, ExecFileException } from 'node:child_process';
import { readFileSync } from 'node:fs';
import path from 'node:path';
import url from 'node:url';

const __dirname = path.dirname(url.fileURLToPath(import.meta.url));

interface ExecOutput {
error: ExecFileException | null;
stdout: string;
stderr: string;
}
/// A lightweight aysnc execFile for fast testing
function execFileAsync(file: string, args: string[] = [], options = {}): Promise<ExecOutput> {
return new Promise((resolve) => {
execFile(file, args, options, (error, stdout, stderr) => {
resolve({ error, stdout, stderr });
});
});
}

describe('Bundled CLI Tests', () => {
// Path to the bin file that imports the bundled js file
const cliPath = path.resolve(__dirname, '../../bin/teraslice-cli.js');

describe('-> teraslice-cli commands', () => {
it('should display top level sub-commands', async () => {
const result = await execFileAsync('node', [cliPath, '--help']);
expect(result.error).toBeNull();
expect(result.stdout).toContain('teraslice-cli.js <command>');
expect(result.stderr).toBe('');
});

it('should display correct version from pkg.json', async () => {
const packageJsonPath = path.join(__dirname, '../../package.json');
const version = JSON.parse(readFileSync(packageJsonPath, { encoding: 'utf-8' })).version;
const result = await execFileAsync('node', [cliPath, '--version']);
expect(result.stdout).toContain(version);
});

it('should display aliases commands', async () => {
const result = await execFileAsync('node', [cliPath, 'aliases']);
expect(result.error?.code).toBe(1);
expect(result.stdout).toBe('');
expect(result.stderr).toContain('teraslice-cli.js aliases <command>');
});

it('should display assets commands', async () => {
const result = await execFileAsync('node', [cliPath, 'assets']);
expect(result.error?.code).toBe(1);
expect(result.stdout).toBe('');
expect(result.stderr).toContain('teraslice-cli.js assets <command>');
});

it('should display jobs commands', async () => {
const result = await execFileAsync('node', [cliPath, 'jobs']);
expect(result.error?.code).toBe(1);
expect(result.stdout).toBe('');
expect(result.stderr).toContain('teraslice-cli.js jobs');
expect(result.stderr).toContain('commands to manage job');
});

it('should display ex commands', async () => {
const result = await execFileAsync('node', [cliPath, 'ex']);
expect(result.error?.code).toBe(1);
expect(result.stdout).toBe('');
expect(result.stderr).toContain('teraslice-cli.js ex <command>');
});

it('should display nodes commands', async () => {
const result = await execFileAsync('node', [cliPath, 'nodes']);
expect(result.error?.code).toBe(1);
expect(result.stdout).toBe('');
expect(result.stderr).toContain('teraslice-cli.js nodes <command>');
});

it('should display workers commands', async () => {
const result = await execFileAsync('node', [cliPath, 'workers']);
expect(result.error?.code).toBe(1);
expect(result.stdout).toBe('');
expect(result.stderr).toContain('teraslice-cli.js workers <command>');
});

it('should display controllers commands', async () => {
const result = await execFileAsync('node', [cliPath, 'controllers']);
expect(result.error?.code).toBe(1);
expect(result.stdout).toBe('');
expect(result.stderr).toContain('teraslice-cli.js controllers <command>');
});

it('should display tjm commands', async () => {
const result = await execFileAsync('node', [cliPath, 'tjm']);
expect(result.error?.code).toBe(1);
expect(result.stdout).toBe('');
expect(result.stderr).toContain('teraslice-cli.js tjm <command>');
});
});

describe('-> User error feedback', () => {
describe('-> assets commands', () => {
describe('-> registry', () => {
it('should tell user directory is invalid', async () => {
const result = await execFileAsync('node', [cliPath, 'assets', 'registry']);
expect(result.error?.code).toBe(1);
expect(result.stdout).toBe('');
expect(result.stderr).toContain('is not a valid asset source directory.');
});
});
describe('-> build', () => {
it('should tell user directory is invalid', async () => {
const result = await execFileAsync('node', [cliPath, 'assets', 'build']);
expect(result.error?.code).toBe(1);
expect(result.stdout).toBe('');
expect(result.stderr).toContain('is not a valid asset source directory.');
expect(result.stderr).toContain('Error building asset');
});
});
});
describe('-> tjm commands', () => {
describe('-> register', () => {
it('should tell user no jobfile was found', async () => {
const result = await execFileAsync('node', [cliPath, 'tjm', 'register', 'localhost', 'non-file3.json']);
expect(result.error?.code).toBe(1);
expect(result.stdout).toBe('');
expect(result.stderr).toContain('check your path and file name and try again');
expect(result.stderr).toContain('Cannot find');
});
});
});
});
});

0 comments on commit a9b09c9

Please sign in to comment.