Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[teraslice-cli] Convert teraslice-cli to compile with esbuild #3851

Merged
merged 8 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 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 <info@terascope.io>",
"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');
});
});
});
});
});
Loading