Skip to content

Finishing the project commands TS port #1449

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

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
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
61 changes: 31 additions & 30 deletions commands/__tests__/project.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// @ts-nocheck
import yargs from 'yargs';
import yargs, { Argv } from 'yargs';
import deploy from '../project/deploy';
import create from '../project/create';
import upload from '../project/upload';
Expand All @@ -8,12 +7,13 @@ import logs from '../project/logs';
import watch from '../project/watch';
import download from '../project/download';
import open from '../project/open';
import * as dev from '../project/dev';
import dev from '../project/dev';
import add from '../project/add';
import migrateApp from '../project/migrateApp';
import migrate from '../project/migrate';
import cloneApp from '../project/cloneApp';
import installDeps from '../project/installDeps';
import projectCommand from '../project';

jest.mock('yargs');
jest.mock('../project/deploy');
Expand All @@ -32,11 +32,12 @@ jest.mock('../project/migrate', () => ({}));
jest.mock('../project/installDeps');
jest.mock('../../lib/commonOpts');

yargs.command.mockReturnValue(yargs);
yargs.demandCommand.mockReturnValue(yargs);

// Import this last so mocks apply
import projectCommand from '../project';
const commandSpy = jest
.spyOn(yargs as Argv, 'command')
.mockReturnValue(yargs as Argv);
const demandCommandSpy = jest
.spyOn(yargs as Argv, 'demandCommand')
.mockReturnValue(yargs as Argv);

describe('commands/project', () => {
describe('command', () => {
Expand All @@ -57,38 +58,38 @@ describe('commands/project', () => {

describe('builder', () => {
const subcommands = [
['create', create],
['add', add],
['watch', watch],
['dev', dev],
['upload', upload],
['deploy', deploy],
['logs', logs],
['listBuilds', listBuilds],
['download', download],
['open', open],
['migrateApp', migrateApp],
['cloneApp', cloneApp],
['installDeps', installDeps],
['migrate', migrate],
create,
add,
watch,
dev,
upload,
deploy,
logs,
listBuilds,
download,
open,
migrateApp,
migrate,
cloneApp,
installDeps,
];

it('should demand the command takes one positional argument', () => {
projectCommand.builder(yargs);
projectCommand.builder(yargs as Argv);

expect(yargs.demandCommand).toHaveBeenCalledTimes(1);
expect(yargs.demandCommand).toHaveBeenCalledWith(1, '');
expect(demandCommandSpy).toHaveBeenCalledTimes(1);
expect(demandCommandSpy).toHaveBeenCalledWith(1, '');
});

it('should add the correct number of sub commands', () => {
projectCommand.builder(yargs);
expect(yargs.command).toHaveBeenCalledTimes(subcommands.length);
projectCommand.builder(yargs as Argv);
expect(commandSpy).toHaveBeenCalledTimes(subcommands.length);
});

it.each(subcommands)('should attach the %s subcommand', (name, module) => {
projectCommand.builder(yargs);
it.each(subcommands)('should attach the %s subcommand', module => {
projectCommand.builder(yargs as Argv);
expect(module).toBeDefined();
expect(yargs.command).toHaveBeenCalledWith(module);
expect(commandSpy).toHaveBeenCalledWith(module);
});
});
});
3 changes: 0 additions & 3 deletions commands/app.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import migrateCommand from './app/migrate';
import { addGlobalOptions } from '../lib/commonOpts';
import { Argv, CommandModule } from 'yargs';

export const command = ['app', 'apps'];
Expand All @@ -8,8 +7,6 @@ export const command = ['app', 'apps'];
export const describe = undefined;

export function builder(yargs: Argv) {
addGlobalOptions(yargs);

return yargs.command(migrateCommand).demandCommand(1, '');
}

Expand Down
45 changes: 18 additions & 27 deletions commands/app/__tests__/migrate.test.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import { ArgumentsCamelCase, Argv } from 'yargs';
import { handler, builder } from '../migrate';
import yargs, { ArgumentsCamelCase, Argv } from 'yargs';
import { PLATFORM_VERSIONS } from '@hubspot/local-dev-lib/constants/projects';
import { logger } from '@hubspot/local-dev-lib/logger';
import { getAccountConfig } from '@hubspot/local-dev-lib/config';
import { migrateApp2025_2, MigrateAppArgs } from '../../../lib/app/migrate';
import { migrateApp2023_2 } from '../../../lib/app/migrate_legacy';
import { logger } from '@hubspot/local-dev-lib/logger';
import { EXIT_CODES } from '../../../lib/enums/exitCodes';
import { PLATFORM_VERSIONS } from '@hubspot/local-dev-lib/constants/projects';
import migrateCommand from '../migrate';

jest.mock('yargs');
jest.mock('@hubspot/local-dev-lib/config');
jest.mock('@hubspot/local-dev-lib/logger');
jest.mock('../../../lib/app/migrate');
jest.mock('../../../lib/app/migrate_legacy');
jest.mock('yargs');
const mockYargs = yargs as Argv;

const mockedGetAccountConfig = getAccountConfig as jest.Mock;
const mockedMigrateApp2023_2 = migrateApp2023_2 as jest.Mock;
const mockedMigrateApp2025_2 = migrateApp2025_2 as jest.Mock;
const mockedLogger = logger as jest.Mocked<typeof logger>;
const optionsSpy = jest.spyOn(mockYargs, 'options');
const exampleSpy = jest.spyOn(mockYargs, 'example');

describe('commands/app/migrate', () => {
const mockAccountId = 123;
Expand All @@ -35,7 +38,7 @@ describe('commands/app/migrate', () => {
it('should exit with error when no account config is found', async () => {
mockedGetAccountConfig.mockReturnValue(null);

await handler({
await migrateCommand.handler({
derivedAccountId: mockAccountId,
} as ArgumentsCamelCase<MigrateAppArgs>);

Expand All @@ -45,7 +48,7 @@ describe('commands/app/migrate', () => {
});

it('should call migrateApp2025_2 for platform version 2025.2', async () => {
await handler({
await migrateCommand.handler({
derivedAccountId: mockAccountId,
platformVersion: PLATFORM_VERSIONS.v2025_2,
} as ArgumentsCamelCase<MigrateAppArgs>);
Expand All @@ -58,7 +61,7 @@ describe('commands/app/migrate', () => {
});

it('should call migrateApp2023_2 for platform version 2023.2', async () => {
await handler({
await migrateCommand.handler({
derivedAccountId: mockAccountId,
platformVersion: PLATFORM_VERSIONS.v2023_2,
} as ArgumentsCamelCase<MigrateAppArgs>);
Expand All @@ -76,7 +79,7 @@ describe('commands/app/migrate', () => {
mockedMigrateApp2023_2.mockRejectedValue(mockError);
const exitSpy = jest.spyOn(process, 'exit').mockImplementation();

await handler({
await migrateCommand.handler({
derivedAccountId: mockAccountId,
platformVersion: PLATFORM_VERSIONS.v2023_2,
} as ArgumentsCamelCase<MigrateAppArgs>);
Expand All @@ -88,22 +91,10 @@ describe('commands/app/migrate', () => {
});

describe('builder', () => {
let mockYargs: Argv;

beforeEach(() => {
mockYargs = {
options: jest.fn().mockReturnThis(),
option: jest.fn().mockReturnThis(),
example: jest.fn().mockReturnThis(),
conflicts: jest.fn().mockReturnThis(),
argv: { _: ['app', 'migrate'] },
} as unknown as Argv;
});

it('should add required options', async () => {
await builder(mockYargs);
await migrateCommand.builder(mockYargs);

expect(mockYargs.options).toHaveBeenCalledWith(
expect(optionsSpy).toHaveBeenCalledWith(
expect.objectContaining({
name: expect.objectContaining({
type: 'string',
Expand All @@ -127,9 +118,9 @@ describe('commands/app/migrate', () => {
});

it('should set default platform version to 2023.2', async () => {
await builder(mockYargs);
await migrateCommand.builder(mockYargs);

expect(mockYargs.options).toHaveBeenCalledWith(
expect(optionsSpy).toHaveBeenCalledWith(
expect.objectContaining({
'platform-version': expect.objectContaining({
default: '2023.2',
Expand All @@ -139,9 +130,9 @@ describe('commands/app/migrate', () => {
});

it('should add example command', async () => {
await builder(mockYargs);
await migrateCommand.builder(mockYargs);

expect(mockYargs.example).toHaveBeenCalledWith([
expect(exampleSpy).toHaveBeenCalledWith([
['$0 app migrate', expect.any(String)],
]);
});
Expand Down
30 changes: 17 additions & 13 deletions commands/app/migrate.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import { logger } from '@hubspot/local-dev-lib/logger';
import { getAccountConfig } from '@hubspot/local-dev-lib/config';
import { PLATFORM_VERSIONS } from '@hubspot/local-dev-lib/constants/projects';
import { ArgumentsCamelCase, Argv, CommandModule } from 'yargs';

import {
addAccountOptions,
addConfigOptions,
addUseEnvironmentOptions,
} from '../../lib/commonOpts';
import { ArgumentsCamelCase, Argv } from 'yargs';
import { YargsCommandModule } from '../../types/Yargs';
import {
trackCommandMetadataUsage,
trackCommandUsage,
Expand All @@ -19,6 +14,7 @@ import { migrateApp2025_2, MigrateAppArgs } from '../../lib/app/migrate';
import { uiBetaTag, uiCommandReference, uiLink } from '../../lib/ui';
import { migrateApp2023_2 } from '../../lib/app/migrate_legacy';
import { getIsInProject } from '../../lib/projects';
import { makeYargsBuilder } from '../../lib/yargsUtils';

const { v2023_2, v2025_2, unstable } = PLATFORM_VERSIONS;
export const validMigrationTargets = [v2023_2, v2025_2, unstable];
Expand Down Expand Up @@ -96,11 +92,7 @@ export async function handler(options: ArgumentsCamelCase<MigrateAppArgs>) {
return process.exit(EXIT_CODES.SUCCESS);
}

export function builder(yargs: Argv): Argv<MigrateAppArgs> {
addConfigOptions(yargs);
addAccountOptions(yargs);
addUseEnvironmentOptions(yargs);

function appMigrateBuilder(yargs: Argv): Argv<MigrateAppArgs> {
yargs.options({
name: {
describe: i18n(
Expand Down Expand Up @@ -138,7 +130,19 @@ export function builder(yargs: Argv): Argv<MigrateAppArgs> {
return yargs as Argv<MigrateAppArgs>;
}

const migrateCommand: CommandModule<unknown, MigrateAppArgs> = {
const builder = makeYargsBuilder<MigrateAppArgs>(
appMigrateBuilder,
command,
uiBetaTag(i18n(`commands.project.subcommands.migrateApp.describe`), false),
{
useGlobalOptions: true,
useConfigOptions: true,
useAccountOptions: true,
useEnvironmentOptions: true,
}
);

const migrateCommand: YargsCommandModule<unknown, MigrateAppArgs> = {
command,
describe,
handler,
Expand Down
58 changes: 35 additions & 23 deletions commands/project.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,29 @@
// @ts-nocheck
const { addGlobalOptions } = require('../lib/commonOpts');
const { i18n } = require('../lib/lang');
const { uiBetaTag } = require('../lib/ui');
const deploy = require('./project/deploy');
const create = require('./project/create');
const upload = require('./project/upload');
const listBuilds = require('./project/listBuilds');
const logs = require('./project/logs');
const watch = require('./project/watch');
const download = require('./project/download');
const open = require('./project/open');
const dev = require('./project/dev');
const add = require('./project/add');
const migrateApp = require('./project/migrateApp');
const migrate = require('./project/migrate');
const cloneApp = require('./project/cloneApp');
const installDeps = require('./project/installDeps');
import { Argv } from 'yargs';
import { i18n } from '../lib/lang';
import { uiBetaTag } from '../lib/ui';
import deploy from './project/deploy';
import create from './project/create';
import upload from './project/upload';
import listBuilds from './project/listBuilds';
import logs from './project/logs';
import watch from './project/watch';
import download from './project/download';
import open from './project/open';
import dev from './project/dev';
import add from './project/add';
import migrate from './project/migrate';
import migrateApp from './project/migrateApp';
import cloneApp from './project/cloneApp';
import installDeps from './project/installDeps';
import { makeYargsBuilder } from '../lib/yargsUtils';
import { YargsCommandModuleBucket } from '../types/Yargs';

const i18nKey = 'commands.project';

exports.command = ['project', 'projects'];
exports.describe = uiBetaTag(i18n(`${i18nKey}.describe`), false);

exports.builder = yargs => {
addGlobalOptions(yargs);
const command = ['project', 'projects'];
const describe = uiBetaTag(i18n(`${i18nKey}.describe`), false);

function projectBuilder(yargs: Argv): Argv {
yargs
.command(create)
.command(add)
Expand All @@ -43,4 +42,17 @@ exports.builder = yargs => {
.demandCommand(1, '');

return yargs;
}

const builder = makeYargsBuilder(projectBuilder, command, describe);

const projectCommand: YargsCommandModuleBucket<unknown, Argv> = {
command,
describe,
builder,
};

export default projectCommand;

// TODO Remove this legacy export once we've migrated all commands to TS
module.exports = projectCommand;
2 changes: 1 addition & 1 deletion commands/project/__tests__/add.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import yargs, { Argv } from 'yargs';
import * as projectAddCommand from '../add';
import projectAddCommand from '../add';

jest.mock('yargs');
jest.mock('../../../lib/commonOpts');
Expand Down
Loading