Skip to content
Open
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
32 changes: 19 additions & 13 deletions lib/utils/remaining-flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ import { Command } from 'commander';

export function getRemainingFlags(cli: Command) {
const rawArgs = [...(cli as any).rawArgs];

const spliceIndex = rawArgs.findIndex((item: string) =>
item.startsWith('--'),
);
if (spliceIndex === -1) {
return [];
}

return rawArgs
.splice(
Math.max(
rawArgs.findIndex((item: string) => item.startsWith('--')),
0,
),
)
.splice(spliceIndex)
.filter((item: string, index: number, array: string[]) => {
// If the option is consumed by commander.js, then we skip it
if (cli.options.find((o: any) => o.short === item || o.long === item)) {
Expand All @@ -18,7 +21,7 @@ export function getRemainingFlags(cli: Command) {
// If it's an argument of an option consumed by commander.js, then we
// skip it too
const prevKeyRaw = array[index - 1];
if (prevKeyRaw) {
if (prevKeyRaw?.startsWith('-')) {
const previousKey = camelCase(
prevKeyRaw.replace(/--/g, '').replace('no', ''),
);
Expand All @@ -40,10 +43,13 @@ export function getRemainingFlags(cli: Command) {
*/

function camelCase(flag: string) {
return flag
.split('-')
.filter((word) => word.length > 0)
.reduce((str, word) => {
return str + word[0].toUpperCase() + word.slice(1);
});
const words = flag.split('-').filter((word) => word.length > 0);

if (words.length === 0) {
return '';
}

return words.reduce((str, word) => {
return str + word[0].toUpperCase() + word.slice(1);
});
}
22 changes: 21 additions & 1 deletion test/e2e/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ export function spawnNest(
args: string,
cwd?: string,
env?: NodeJS.ProcessEnv,
cliPath?: string,
): { child: ChildProcess; output: () => string; kill: () => void } {
const child = spawn('node', [CLI_PATH, ...args.split(/\s+/)], {
const child = spawn('node', [cliPath ?? CLI_PATH, ...args.split(/\s+/)], {
cwd: cwd ?? process.cwd(),
env: { ...process.env, ...env },
stdio: ['pipe', 'pipe', 'pipe'],
Expand Down Expand Up @@ -551,3 +552,22 @@ export function removeLocalCli(appPath: string): void {
fs.rmSync(localCli, { recursive: true, force: true });
}
}

/**
* Creates a symlink for the nest cli script path,
* useful when testing different execution paths
* @param linkPath the desired symlink path
*/
export function createCliSymlink(linkPath: string) {
fs.symlinkSync(CLI_PATH, linkPath, 'file');
}

/**
* removes an existing link
* @param linkPath the link
*/
export function removelink(linkPath: string) {
if (fs.existsSync(linkPath)) {
fs.unlinkSync(linkPath);
}
}
32 changes: 32 additions & 0 deletions test/e2e/start.command.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { execSync } from 'child_process';
import * as path from 'path';
import * as crypto from 'crypto';
import { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest';
import {
convertToCjs,
Expand All @@ -11,6 +12,7 @@ import {
installWebpackDeps,
readFileContent,
removeLocalCli,
removelink,
removeTempDir,
runNest,
runNestRaw,
Expand All @@ -20,6 +22,7 @@ import {
spawnNest,
waitFor,
writeFileContent,
createCliSymlink,
} from './helpers.js';

describe('Start Command - CJS project (e2e)', () => {
Expand Down Expand Up @@ -178,6 +181,35 @@ describe('Start Command - CJS project (e2e)', () => {
}
});

it('should start when the cli path contains "-no-"', async () => {
const port = 4070;

const randomChars = crypto.randomBytes(8).toString('hex');
const pathWithNo = path.join(tmpDir, `nest-no-test-cli${randomChars}`);

createCliSymlink(pathWithNo);

const proc = spawnNest(
'start',
appPath,
{ PORT: String(port) },
pathWithNo,
);

try {
await waitFor(
() => proc.output().includes('Nest application successfully started'),
60_000,
);

const response = await httpGet(`http://127.0.0.1:${port}`);
expect(response.status).toBe(200);
} finally {
proc.kill();
removelink(pathWithNo);
}
});

describe('with SWC builder', () => {
beforeAll(() => {
execSync('npm install --save-dev @swc/cli @swc/core', {
Expand Down
23 changes: 19 additions & 4 deletions test/lib/utils/remaining-flags.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,29 @@ describe('getRemainingFlags', () => {
expect(result).toEqual([]);
});

it('should return all raw args when no flags starting with "--" are present', () => {
// When no "--" flags exist, findIndex returns -1, Math.max(-1, 0) = 0,
// so splice(0) returns all elements from the rawArgs array
it('should return an empty array when no flags starting with "--" are present', () => {
// When no "--" flags exist, findIndex returns -1, which means no flags are present.
// terminate early when no flags are found
const cmd = createCommand(['node', 'nest', 'start'], []);

const result = getRemainingFlags(cmd);

expect(result).toEqual(['node', 'nest', 'start']);
expect(result).toEqual([]);
});

it('should not crash when flags containing -no- exist', () => {
const cmd = createCommand([
'node',
'nest',
'start',
'--watch',
'-no-',
'value',
]);

const result = getRemainingFlags(cmd);

expect(result).toEqual(['--watch', '-no-', 'value']);
});

it('should filter out short option flags consumed by commander', () => {
Expand Down