mirror of
https://github.com/tiktok/sparo.git
synced 2024-11-23 07:38:15 -05:00
Merge pull request #83 from tiktok/feat-sparo-completion
[sparo] shell completion feature
This commit is contained in:
commit
30a6efef00
89 changed files with 1099 additions and 136 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -63,6 +63,8 @@ jspm_packages/
|
|||
.idea/
|
||||
*.iml
|
||||
|
||||
.nvmrc
|
||||
|
||||
# Rush temporary files
|
||||
common/deploy/
|
||||
common/temp/
|
||||
|
|
|
@ -19,6 +19,8 @@ export class SparoCommandLine {
|
|||
private constructor() {}
|
||||
|
||||
public static async launchAsync(launchOptions: ILaunchOptions): Promise<void> {
|
||||
const isCompletion: boolean = ['completion', '--get-yargs-completions'].includes(process.argv[2]);
|
||||
if (!isCompletion) {
|
||||
if (launchOptions.collectTelemetryAsync) {
|
||||
const telemetryService: TelemetryService = await getFromContainerAsync(TelemetryService);
|
||||
telemetryService.setCollectTelemetryFunction(launchOptions.collectTelemetryAsync);
|
||||
|
@ -36,6 +38,7 @@ export class SparoCommandLine {
|
|||
await getFromContainerAsync(GitSparseCheckoutService);
|
||||
gitSparseCheckoutService.setAdditionalSkeletonFolders(launchOptions.additionalSkeletonFolders);
|
||||
}
|
||||
}
|
||||
|
||||
const sparo: SparoCommandLine = new SparoCommandLine();
|
||||
await sparo.prepareCommandAsync();
|
||||
|
@ -79,6 +82,6 @@ export class SparoCommandLine {
|
|||
}
|
||||
|
||||
private _supportedCommand(commandName: string): boolean {
|
||||
return this._commandsMap.has(commandName);
|
||||
return this._commandsMap.has(commandName) || commandName === 'completion';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,18 @@ export class AutoConfigCommand implements ICommand<IAutoConfigCommandOptions> {
|
|||
type: 'boolean',
|
||||
hidden: true,
|
||||
default: false
|
||||
})
|
||||
.completion('completion', false, (current, argv, done) => {
|
||||
const longParameters: string[] = [argv.overwrite ? '' : '--overwrite'].filter(Boolean);
|
||||
if (current.startsWith('--')) {
|
||||
done(
|
||||
longParameters.filter((parameter) => {
|
||||
return parameter.startsWith(current);
|
||||
})
|
||||
);
|
||||
} else {
|
||||
done([]);
|
||||
}
|
||||
});
|
||||
}
|
||||
public handler = async (
|
||||
|
@ -52,7 +64,4 @@ export class AutoConfigCommand implements ICommand<IAutoConfigCommandOptions> {
|
|||
throw e;
|
||||
}
|
||||
};
|
||||
public getHelp(): string {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,5 +17,4 @@ export interface ICommand<O extends {}> {
|
|||
|
||||
builder: (yargs: Argv<O>) => void;
|
||||
handler: (args: ArgumentsCamelCase<O>, terminalService: TerminalService) => Promise<void>;
|
||||
getHelp: () => string;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import * as child_process from 'child_process';
|
||||
import { inject } from 'inversify';
|
||||
import { JsonFile } from '@rushstack/node-core-library';
|
||||
import { Command } from '../../decorator';
|
||||
import { GitService } from '../../services/GitService';
|
||||
import { GitRemoteFetchConfigService } from '../../services/GitRemoteFetchConfigService';
|
||||
|
@ -32,7 +33,7 @@ export class CheckoutCommand implements ICommand<ICheckoutCommandOptions> {
|
|||
@inject(GitRemoteFetchConfigService) private _gitRemoteFetchConfigService!: GitRemoteFetchConfigService;
|
||||
@inject(SparoProfileService) private _sparoProfileService!: SparoProfileService;
|
||||
|
||||
public builder(yargs: Argv<{}>): void {
|
||||
public builder = (yargs: Argv<{}>): void => {
|
||||
/**
|
||||
* git checkout [-q] [-f] [-m] [<branch>]
|
||||
* git checkout [-q] [-f] [-m] --detach [<branch>]
|
||||
|
@ -98,8 +99,114 @@ export class CheckoutCommand implements ICommand<ICheckoutCommandOptions> {
|
|||
default: [],
|
||||
description:
|
||||
'Checkout projects downstream from (and including itself and all its dependencies) project <from..>, can be used together with option --profile/--add-profile to form a union selection of the two options. The projects selectors here will never replace what have been checked out by profiles'
|
||||
});
|
||||
})
|
||||
.completion('completion', false, (current, argv, done) => {
|
||||
const isNoProfile: boolean = argv.profile.some(
|
||||
(profile: string | boolean) => typeof profile === 'boolean' && profile === false
|
||||
);
|
||||
const shortParameters: string[] = [argv.b ? '' : '-b', argv.B ? '' : '-B'].filter(Boolean);
|
||||
const longParameters: string[] = [
|
||||
isNoProfile ? '' : '--no-profile',
|
||||
isNoProfile ? '' : '--profile',
|
||||
isNoProfile ? '' : '--add-profile',
|
||||
'--to',
|
||||
'--from'
|
||||
].filter(Boolean);
|
||||
|
||||
if (current === '-') {
|
||||
done(shortParameters);
|
||||
} else if (current === '--') {
|
||||
done(longParameters);
|
||||
} else if (current === '--profile' || current === '--add-profile') {
|
||||
const profileNameSet: Set<string> = new Set(this._sparoProfileService.loadProfileNames());
|
||||
for (const profile of argv.profile) {
|
||||
if (typeof profile === 'string') {
|
||||
profileNameSet.delete(profile);
|
||||
}
|
||||
}
|
||||
for (const profile of argv.addProfile) {
|
||||
if (typeof profile === 'string') {
|
||||
profileNameSet.delete(profile);
|
||||
}
|
||||
}
|
||||
done(Array.from(profileNameSet));
|
||||
} else if (current === '--to' || current === '--from') {
|
||||
let rushJson: { projects?: { packageName: string }[] } = {};
|
||||
const root: string = this._gitService.getRepoInfo().root;
|
||||
try {
|
||||
rushJson = JsonFile.load(`${root}/rush.json`);
|
||||
} catch (e) {
|
||||
// no-catch
|
||||
}
|
||||
if (Array.isArray(rushJson.projects)) {
|
||||
const packageNameSet: Set<string> = new Set<string>(
|
||||
rushJson.projects.map((project) => project.packageName)
|
||||
);
|
||||
if (current === '--to') {
|
||||
for (const packageName of argv.to) {
|
||||
packageNameSet.delete(packageName);
|
||||
}
|
||||
}
|
||||
if (current === '--from') {
|
||||
for (const packageName of argv.from) {
|
||||
packageNameSet.delete(packageName);
|
||||
}
|
||||
}
|
||||
const packageNames: string[] = Array.from(packageNameSet).sort();
|
||||
if (process.cwd() !== root) {
|
||||
packageNames.unshift('.');
|
||||
}
|
||||
done(packageNames);
|
||||
}
|
||||
done([]);
|
||||
} else if (current.startsWith('--')) {
|
||||
done(longParameters.filter((parameter) => parameter.startsWith(current)));
|
||||
} else {
|
||||
const previous: string = process.argv.slice(-2)[0];
|
||||
if (previous === '--profile' || previous === '--add-profile') {
|
||||
const profileNameSet: Set<string> = new Set(this._sparoProfileService.loadProfileNames());
|
||||
for (const profile of argv.profile) {
|
||||
if (typeof profile === 'string') {
|
||||
profileNameSet.delete(profile);
|
||||
}
|
||||
}
|
||||
for (const profile of argv.addProfile) {
|
||||
if (typeof profile === 'string') {
|
||||
profileNameSet.delete(profile);
|
||||
}
|
||||
}
|
||||
done(Array.from(profileNameSet).filter((profileName) => profileName.startsWith(current)));
|
||||
} else if (previous === '--to' || previous === '--from') {
|
||||
let rushJson: { projects?: { packageName: string }[] } = {};
|
||||
const root: string = this._gitService.getRepoInfo().root;
|
||||
try {
|
||||
rushJson = JsonFile.load(`${root}/rush.json`);
|
||||
} catch (e) {
|
||||
// no-catch
|
||||
}
|
||||
if (Array.isArray(rushJson.projects)) {
|
||||
const packageNameSet: Set<string> = new Set<string>(
|
||||
rushJson.projects.map((project) => project.packageName)
|
||||
);
|
||||
if (previous === '--to') {
|
||||
for (const packageName of argv.to) {
|
||||
packageNameSet.delete(packageName);
|
||||
}
|
||||
}
|
||||
if (previous === '--from') {
|
||||
for (const packageName of argv.from) {
|
||||
packageNameSet.delete(packageName);
|
||||
}
|
||||
}
|
||||
const packageNames: string[] = Array.from(packageNameSet).sort();
|
||||
done(packageNames.filter((packageName) => packageName.startsWith(current)));
|
||||
}
|
||||
done([]);
|
||||
}
|
||||
done([]);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
public handler = async (
|
||||
args: ArgumentsCamelCase<ICheckoutCommandOptions>,
|
||||
|
@ -258,10 +365,6 @@ export class CheckoutCommand implements ICommand<ICheckoutCommandOptions> {
|
|||
}
|
||||
};
|
||||
|
||||
public getHelp(): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
private _ensureBranchInLocal(branch: string): boolean {
|
||||
// fetch from remote
|
||||
const remote: string = this._gitService.getBranchRemote(branch);
|
||||
|
|
|
@ -44,7 +44,4 @@ export class CICheckoutCommand implements ICommand<ICICheckoutCommandOptions> {
|
|||
from
|
||||
});
|
||||
};
|
||||
public getHelp(): string {
|
||||
return 'sparse help';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,8 +71,4 @@ export class CICloneCommand implements ICommand<ICloneCommandOptions> {
|
|||
|
||||
terminal.writeLine(`Remember to run "cd ${directory}"`);
|
||||
};
|
||||
|
||||
public getHelp(): string {
|
||||
return `clone help`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ export interface ICloneCommandOptions {
|
|||
@Command()
|
||||
export class CloneCommand implements ICommand<ICloneCommandOptions> {
|
||||
public cmd: string = 'clone <repository> [directory]';
|
||||
public description: string = '';
|
||||
public description: string = 'Clone a repository into a new directory';
|
||||
|
||||
@inject(GitService) private _gitService!: GitService;
|
||||
@inject(GitCloneService) private _gitCloneService!: GitCloneService;
|
||||
|
@ -61,6 +61,17 @@ export class CloneCommand implements ICommand<ICloneCommandOptions> {
|
|||
return 'You must specify a repository to clone.';
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.completion('completion', false, (current, argv, done) => {
|
||||
const longParameters: string[] = ['--profile', '--branch', '--full', '--skip-git-config'];
|
||||
|
||||
if (current === '--') {
|
||||
done(longParameters);
|
||||
} else if (current.startsWith('--')) {
|
||||
done(longParameters.filter((parameter) => parameter.startsWith(current)));
|
||||
} else {
|
||||
done([]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -154,8 +165,4 @@ export class CloneCommand implements ICommand<ICloneCommandOptions> {
|
|||
terminal.writeLine(' ' + Colorize.cyan('sparo init-profile --profile <profile_name>'));
|
||||
}
|
||||
};
|
||||
|
||||
public getHelp(): string {
|
||||
return `clone help`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ export class FetchCommand implements ICommand<IFetchCommandOptions> {
|
|||
@inject(GitService) private _gitService!: GitService;
|
||||
@inject(GitRemoteFetchConfigService) private _gitRemoteFetchConfigService!: GitRemoteFetchConfigService;
|
||||
|
||||
public builder(yargs: Argv<{}>): void {
|
||||
public builder = (yargs: Argv<{}>): void => {
|
||||
/**
|
||||
* sparo fetch <remote> <branch> [--all]
|
||||
*/
|
||||
|
@ -31,8 +31,31 @@ export class FetchCommand implements ICommand<IFetchCommandOptions> {
|
|||
.positional('branch', { type: 'string' })
|
||||
.string('remote')
|
||||
.string('branch')
|
||||
.boolean('all');
|
||||
.boolean('all')
|
||||
.completion('completion', false, (current, argv, done) => {
|
||||
const longParameters: string[] = [argv.all ? '' : '--all', argv.tags ? '' : '--tags'].filter(Boolean);
|
||||
if (current === 'fetch') {
|
||||
done(['origin']);
|
||||
} else if (current === 'origin') {
|
||||
const branchNames: string[] = this._gitRemoteFetchConfigService.getBranchNamesFromRemote('origin');
|
||||
branchNames.unshift('HEAD');
|
||||
done(branchNames);
|
||||
} else if (current === '--') {
|
||||
done(longParameters);
|
||||
} else if (current.startsWith('--')) {
|
||||
done(longParameters.filter((parameter) => parameter.startsWith(current)));
|
||||
} else {
|
||||
const previous: string = process.argv.slice(-2)[0];
|
||||
if (previous === 'origin') {
|
||||
const branchNames: string[] =
|
||||
this._gitRemoteFetchConfigService.getBranchNamesFromRemote('origin');
|
||||
branchNames.unshift('HEAD');
|
||||
done(branchNames.filter((name) => name.startsWith(current)));
|
||||
}
|
||||
done([]);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
public handler = async (
|
||||
args: ArgumentsCamelCase<IFetchCommandOptions>,
|
||||
|
@ -75,8 +98,4 @@ export class FetchCommand implements ICommand<IFetchCommandOptions> {
|
|||
|
||||
restoreSingleBranchCallback?.();
|
||||
};
|
||||
|
||||
public getHelp(): string {
|
||||
return `fetch help`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,8 +27,4 @@ export class GitCheckoutCommand implements ICommand<{}> {
|
|||
terminal.writeDebugLine(`proxy args in git-checkout command: ${JSON.stringify(rawArgs)}`);
|
||||
gitService.executeGitCommand({ args: rawArgs });
|
||||
};
|
||||
|
||||
public getHelp(): string {
|
||||
return `git-checkout help`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,8 +27,4 @@ export class GitCloneCommand implements ICommand<{}> {
|
|||
terminal.writeDebugLine(`proxy args in git-clone command: ${JSON.stringify(rawArgs)}`);
|
||||
gitService.executeGitCommand({ args: rawArgs });
|
||||
};
|
||||
|
||||
public getHelp(): string {
|
||||
return `git-clone help`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,8 +28,4 @@ export class GitFetchCommand implements ICommand<{}> {
|
|||
terminal.writeDebugLine(`proxy args in git-fetch command: ${JSON.stringify(rawArgs)}`);
|
||||
gitService.executeGitCommand({ args: rawArgs });
|
||||
};
|
||||
|
||||
public getHelp(): string {
|
||||
return `git-fetch help`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,4 @@ export class GitPullCommand implements ICommand<{}> {
|
|||
terminal.writeDebugLine(`proxy args in git-pull command: ${JSON.stringify(rawArgs)}`);
|
||||
gitService.executeGitCommand({ args: rawArgs });
|
||||
};
|
||||
public getHelp(): string {
|
||||
return `git-pull help`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,18 @@ export class InitProfileCommand implements ICommand<IInitProjectCommandOptions>
|
|||
description: 'The name of the profile to initialize.'
|
||||
})
|
||||
.demandOption(['profile'])
|
||||
.usage('Usage: $0 init-profile --profile <profile>');
|
||||
.usage('Usage: $0 init-profile --profile <profile>')
|
||||
.completion('completion', false, (current, argv, done) => {
|
||||
const longParameters: string[] = [argv.profile ? '' : '--profile'].filter(Boolean);
|
||||
|
||||
if (current === '--') {
|
||||
done(longParameters);
|
||||
} else if (current.startsWith('--')) {
|
||||
done(longParameters.filter((parameter) => parameter.startsWith(current)));
|
||||
} else {
|
||||
done([]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public handler = async (
|
||||
|
@ -66,8 +77,4 @@ export class InitProfileCommand implements ICommand<IInitProjectCommandOptions>
|
|||
this._terminalService.terminal.writeLine();
|
||||
this._terminalService.terminal.writeLine(' ' + Colorize.cyan(destinationPath));
|
||||
};
|
||||
|
||||
public getHelp(): string {
|
||||
return 'init-profile help';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import childProcess from 'child_process';
|
||||
import { Sort } from '@rushstack/node-core-library';
|
||||
import { JsonFile, Sort } from '@rushstack/node-core-library';
|
||||
import { inject } from 'inversify';
|
||||
import { SparoProfileService } from '../../services/SparoProfileService';
|
||||
import { ICommand } from './base';
|
||||
import { Command } from '../../decorator';
|
||||
import { GitService } from '../../services/GitService';
|
||||
import { GitSparseCheckoutService } from '../../services/GitSparseCheckoutService';
|
||||
|
||||
import type { Argv, ArgumentsCamelCase } from 'yargs';
|
||||
|
@ -24,14 +25,58 @@ export class ListProfilesCommand implements ICommand<IListProfilesCommandOptions
|
|||
public description: string =
|
||||
'List all available profiles or query profiles that contain the specified project name';
|
||||
@inject(SparoProfileService) private _sparoProfileService!: SparoProfileService;
|
||||
@inject(GitService) private _gitService!: GitService;
|
||||
@inject(GitSparseCheckoutService) private _gitSparseCheckoutService!: GitSparseCheckoutService;
|
||||
|
||||
public builder(yargs: Argv<IListProfilesCommandOptions>): void {
|
||||
yargs.option('project', {
|
||||
public builder = (yargs: Argv<IListProfilesCommandOptions>): void => {
|
||||
yargs
|
||||
.option('project', {
|
||||
type: 'string',
|
||||
description: 'List all profiles contains this specified project name'
|
||||
});
|
||||
})
|
||||
.completion('completion', false, (current, argv, done) => {
|
||||
const longParameters: string[] = [argv.project ? '' : '--project'].filter(Boolean);
|
||||
if (current === '--') {
|
||||
done(longParameters);
|
||||
} else if (current === '--project') {
|
||||
let rushJson: { projects?: { packageName: string }[] } = {};
|
||||
const root: string = this._gitService.getRepoInfo().root;
|
||||
try {
|
||||
rushJson = JsonFile.load(`${root}/rush.json`);
|
||||
} catch (e) {
|
||||
// no-catch
|
||||
}
|
||||
if (Array.isArray(rushJson.projects)) {
|
||||
const packageNameSet: Set<string> = new Set<string>(
|
||||
rushJson.projects.map((project) => project.packageName)
|
||||
);
|
||||
const packageNames: string[] = Array.from(packageNameSet).sort();
|
||||
done(packageNames);
|
||||
}
|
||||
} else if (current.startsWith('--')) {
|
||||
done(longParameters.filter((parameter) => parameter.startsWith(current)));
|
||||
} else {
|
||||
const previous: string = process.argv.slice(-2)[0];
|
||||
if (previous === '--project') {
|
||||
let rushJson: { projects?: { packageName: string }[] } = {};
|
||||
const root: string = this._gitService.getRepoInfo().root;
|
||||
try {
|
||||
rushJson = JsonFile.load(`${root}/rush.json`);
|
||||
} catch (e) {
|
||||
// no-catch
|
||||
}
|
||||
if (Array.isArray(rushJson.projects)) {
|
||||
const packageNameSet: Set<string> = new Set<string>(
|
||||
rushJson.projects.map((project) => project.packageName)
|
||||
);
|
||||
const packageNames: string[] = Array.from(packageNameSet).sort();
|
||||
done(packageNames.filter((packageName) => packageName.startsWith(current)));
|
||||
}
|
||||
}
|
||||
done([]);
|
||||
}
|
||||
});
|
||||
};
|
||||
public handler = async (
|
||||
args: ArgumentsCamelCase<IListProfilesCommandOptions>,
|
||||
terminalService: TerminalService
|
||||
|
@ -85,7 +130,4 @@ export class ListProfilesCommand implements ICommand<IListProfilesCommandOptions
|
|||
}
|
||||
}
|
||||
};
|
||||
public getHelp(): string {
|
||||
return 'list-profiles help';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,55 @@ export class PullCommand implements ICommand<IPullCommandOptions> {
|
|||
})
|
||||
.array('profile')
|
||||
.default('profile', [])
|
||||
.option('get-yargs-completions', {
|
||||
hidden: true,
|
||||
type: 'boolean'
|
||||
})
|
||||
.completion('completion', false, (current, argv, done) => {
|
||||
const isNoProfile: boolean = argv.profile.some(
|
||||
(profile: string | boolean) => typeof profile === 'boolean' && profile === false
|
||||
);
|
||||
const longParameters: string[] = [
|
||||
isNoProfile ? '' : '--profile',
|
||||
isNoProfile ? '' : '--no-profile'
|
||||
].filter(Boolean);
|
||||
if (current === 'pull') {
|
||||
done(['origin']);
|
||||
} else if (current === 'origin') {
|
||||
const branchNames: string[] = this._gitRemoteFetchConfigService.getBranchNamesFromRemote('origin');
|
||||
branchNames.unshift('HEAD');
|
||||
done(branchNames);
|
||||
} else if (current === '--') {
|
||||
done(longParameters);
|
||||
} else if (current === '--profile') {
|
||||
const profileNameSet: Set<string> = new Set(this._sparoProfileService.loadProfileNames());
|
||||
for (const profile of argv.profile) {
|
||||
if (typeof profile === 'string') {
|
||||
profileNameSet.delete(profile);
|
||||
}
|
||||
}
|
||||
done(Array.from(profileNameSet));
|
||||
} else if (current.startsWith('--')) {
|
||||
done(longParameters.filter((parameter) => parameter.startsWith(current)));
|
||||
} else {
|
||||
const previous: string = process.argv.slice(-2)[0];
|
||||
if (previous === '--profile') {
|
||||
const profileNameSet: Set<string> = new Set(this._sparoProfileService.loadProfileNames());
|
||||
for (const profile of argv.profile) {
|
||||
if (typeof profile === 'string') {
|
||||
profileNameSet.delete(profile);
|
||||
}
|
||||
}
|
||||
done(Array.from(profileNameSet).filter((profileName) => profileName.startsWith(current)));
|
||||
} else if (previous === 'origin') {
|
||||
const branchNames: string[] =
|
||||
this._gitRemoteFetchConfigService.getBranchNamesFromRemote('origin');
|
||||
branchNames.unshift('HEAD');
|
||||
done(branchNames.filter((name) => name.startsWith(current)));
|
||||
}
|
||||
done([]);
|
||||
}
|
||||
})
|
||||
.parserConfiguration({ 'unknown-options-as-args': true })
|
||||
.usage(
|
||||
'$0 pull [options] [repository] [refsepc...] [--profile <profile_name> | --no-profile]' +
|
||||
|
@ -105,8 +154,4 @@ export class PullCommand implements ICommand<IPullCommandOptions> {
|
|||
isProfileRestoreFromLocal
|
||||
});
|
||||
};
|
||||
|
||||
public getHelp(): string {
|
||||
return `pull help`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import * as path from 'path';
|
||||
import { inject } from 'inversify';
|
||||
import { FileSystem, FolderItem } from '@rushstack/node-core-library';
|
||||
import { Service } from '../decorator';
|
||||
import yargs, { type MiddlewareFunction, type Argv } from 'yargs';
|
||||
import yargs, { type MiddlewareFunction, type Argv, type AsyncCompletionFunction } from 'yargs';
|
||||
import { TerminalService } from './TerminalService';
|
||||
import { CommandService, type ICommandInfo } from './CommandService';
|
||||
import { getFromContainer } from '../di/container';
|
||||
|
||||
@Service()
|
||||
export class ArgvService {
|
||||
|
@ -34,6 +38,7 @@ export class ArgvService {
|
|||
// --verbose
|
||||
.boolean('verbose')
|
||||
.middleware([this._terminalMiddleware])
|
||||
.completion('completion', false, this._completionFunction)
|
||||
.parseAsync();
|
||||
}
|
||||
|
||||
|
@ -63,4 +68,123 @@ export class ArgvService {
|
|||
this._terminalService.setIsVerbose(verbose);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* To test completion, run
|
||||
*
|
||||
* sparo --get-yargs-completions sparo <command> ...
|
||||
*/
|
||||
private _completionFunction: AsyncCompletionFunction = async (current, argv, done): Promise<void> => {
|
||||
const nativeGitCommands: ICommandInfo[] = [
|
||||
{
|
||||
name: 'add',
|
||||
description: 'add file contents to the index'
|
||||
},
|
||||
{ name: 'branch', description: 'list, create, or delete branches' },
|
||||
{ name: 'checkout', description: 'checkout a branch or paths to the working tree' },
|
||||
{ name: 'clone', description: 'clone a repository into a new directory' },
|
||||
{ name: 'commit', description: 'record changes to the repository' },
|
||||
{ name: 'diff', description: 'show changes between commits, commit and working tree, etc' },
|
||||
{ name: 'fetch', description: 'download objects and refs from another repository' },
|
||||
{ name: 'log', description: 'show commit logs' },
|
||||
{ name: 'merge', description: 'join two or more development histories together' },
|
||||
{ name: 'pull', description: 'fetch from and merge with another repository or a local branch' },
|
||||
{ name: 'push', description: 'update remote refs along with associated objects' },
|
||||
{ name: 'rebase', description: 'forward-port local commits to the updated upstream head' },
|
||||
{ name: 'reset', description: 'reset current HEAD to the specified state' },
|
||||
{ name: 'restore', description: 'restore working tree files' },
|
||||
{ name: 'status', description: 'show the working tree status' }
|
||||
];
|
||||
|
||||
const commandService: CommandService = getFromContainer(CommandService);
|
||||
const { commandInfos: sparoCommandInfos } = commandService;
|
||||
const finalCommands: ICommandInfo[] = Array.from(sparoCommandInfos.values());
|
||||
for (const nativeGitCommand of nativeGitCommands) {
|
||||
if (sparoCommandInfos.has(nativeGitCommand.name)) {
|
||||
continue;
|
||||
}
|
||||
finalCommands.push(nativeGitCommand);
|
||||
}
|
||||
const finalCommandNameSet: Set<string> = new Set<string>(finalCommands.map((x) => x.name));
|
||||
const userInputCmdName: string = argv._[1] || '';
|
||||
|
||||
if (current.includes('sparo')) {
|
||||
// top level completion
|
||||
done(finalCommands.map(({ name, description }) => `${name}:${description}`));
|
||||
} else if (finalCommandNameSet.has(userInputCmdName)) {
|
||||
switch (current) {
|
||||
case 'add': {
|
||||
done(this._getFileCompletions());
|
||||
break;
|
||||
}
|
||||
case 'commit':
|
||||
case 'branch':
|
||||
case 'diff':
|
||||
case 'log':
|
||||
case 'merge':
|
||||
case 'rebase':
|
||||
case 'restore':
|
||||
case 'status': {
|
||||
// TODO: completion for the following commands seem of less use, implement on demand
|
||||
done([]);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
switch (userInputCmdName) {
|
||||
case 'add': {
|
||||
done(this._getFileCompletions(current));
|
||||
break;
|
||||
}
|
||||
case 'rebase': {
|
||||
const shortParameters: string[] = [argv.i || argv.interactive ? '' : '-i'].filter(Boolean);
|
||||
const longParameters: string[] = [
|
||||
argv.continue ? '' : '--continue',
|
||||
argv.skip ? '' : '--skip',
|
||||
argv.abort ? '' : '--abort',
|
||||
argv.i || argv.interactive ? '' : '--interactive'
|
||||
].filter(Boolean);
|
||||
if (current === '-') {
|
||||
done(shortParameters);
|
||||
} else if (current === '--') {
|
||||
done(longParameters);
|
||||
} else if (current.startsWith('--')) {
|
||||
done(longParameters.filter((parameter) => parameter.startsWith(current)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (current) {
|
||||
done(
|
||||
finalCommands
|
||||
.filter(({ name }) => name.startsWith(current))
|
||||
.map(({ name, description }) => `${name}:${description}`)
|
||||
);
|
||||
}
|
||||
done([]);
|
||||
};
|
||||
|
||||
private _getFileCompletions(partial: string = ''): string[] {
|
||||
const dir: string = partial.endsWith('/') ? partial : path.dirname(partial);
|
||||
const base: string = partial.endsWith('/') ? '' : path.basename(partial);
|
||||
try {
|
||||
const items: FolderItem[] = FileSystem.readFolderItems(dir);
|
||||
return items
|
||||
.filter((item) => item.name.startsWith(base))
|
||||
.map((item) => {
|
||||
if (item.isDirectory()) {
|
||||
return path.join(dir, item.name, '/');
|
||||
}
|
||||
return path.join(dir, item.name);
|
||||
});
|
||||
} catch (e) {
|
||||
// Return empty result if encounter issues
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { inject } from 'inversify';
|
||||
import type { Argv } from 'yargs';
|
||||
import type { ICommand } from '../cli/commands/base';
|
||||
import { HelpTextService } from './HelpTextService';
|
||||
import { TerminalService } from './TerminalService';
|
||||
import { Service } from '../decorator';
|
||||
import { ArgvService } from './ArgvService';
|
||||
|
@ -11,23 +10,31 @@ import { getCommandName } from '../cli/commands/util';
|
|||
|
||||
export interface ICommandServiceParams {
|
||||
yargs: Argv<{}>;
|
||||
helpTextService: HelpTextService;
|
||||
terminalService: TerminalService;
|
||||
}
|
||||
|
||||
export interface ICommandInfo {
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
@Service()
|
||||
export class CommandService {
|
||||
@inject(ArgvService) private _yargs!: ArgvService;
|
||||
@inject(HelpTextService) private _helpTextService!: HelpTextService;
|
||||
@inject(TerminalService) private _terminalService!: TerminalService;
|
||||
@inject(TelemetryService) private _telemetryService!: TelemetryService;
|
||||
private _hasInternalError: boolean = false;
|
||||
private _commandInfos: Map<string, ICommandInfo> = new Map<string, ICommandInfo>();
|
||||
|
||||
public register<O extends {}>(command: ICommand<O>): void {
|
||||
const { cmd, description, builder, handler, getHelp } = command;
|
||||
const { cmd, description, builder, handler } = command;
|
||||
const { _terminalService: terminalService } = this;
|
||||
const { terminal } = terminalService;
|
||||
const commandName: string = getCommandName(cmd);
|
||||
this._commandInfos.set(commandName, {
|
||||
name: commandName,
|
||||
description
|
||||
});
|
||||
this._yargs.yargsArgv.command<O>(
|
||||
cmd,
|
||||
description,
|
||||
|
@ -63,10 +70,13 @@ export class CommandService {
|
|||
}
|
||||
}
|
||||
);
|
||||
this._helpTextService.set(commandName, getHelp());
|
||||
}
|
||||
|
||||
public setHasInternalError(): void {
|
||||
this._hasInternalError = true;
|
||||
}
|
||||
|
||||
public get commandInfos(): Map<string, ICommandInfo> {
|
||||
return this._commandInfos;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -128,6 +128,10 @@ export class GitRemoteFetchConfigService {
|
|||
|
||||
/**
|
||||
* Reads remote.origin.fetch from git configuration. It returns a mapping
|
||||
*
|
||||
* Map {
|
||||
* 'master' => Set { '+refs/heads/master:refs/remotes/origin/master' }
|
||||
* }
|
||||
*/
|
||||
public getBranchesInfoFromRemoteFetchConfig(remoteFetchConfig: string[]): Map<string, Set<string>> {
|
||||
const branchRegExp: RegExp = /^(?:\+)?refs\/heads\/([^:]+):/;
|
||||
|
@ -149,6 +153,17 @@ export class GitRemoteFetchConfigService {
|
|||
return branchToValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used for completion
|
||||
*/
|
||||
public getBranchNamesFromRemote(remote: string): string[] {
|
||||
const remoteFetchConfig: string[] | undefined = this._getRemoteFetchInGitConfig(remote);
|
||||
if (!remoteFetchConfig) {
|
||||
return [];
|
||||
}
|
||||
return Array.from(this.getBranchesInfoFromRemoteFetchConfig(remoteFetchConfig).keys());
|
||||
}
|
||||
|
||||
private _getRemoteFetchInGitConfig(remote: string): string[] | undefined {
|
||||
const result: string | undefined = this._gitService.getGitConfig(`remote.${remote}.fetch`, {
|
||||
array: true
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
import { inject } from 'inversify';
|
||||
import { Service } from '../decorator';
|
||||
import { TerminalService } from './TerminalService';
|
||||
|
||||
export interface IHelpTextParams {
|
||||
terminalService: TerminalService;
|
||||
}
|
||||
@Service()
|
||||
export class HelpTextService {
|
||||
@inject(TerminalService) private _terminalService!: TerminalService;
|
||||
|
||||
public helpTextMap: Map<string, string> = new Map<string, string>();
|
||||
|
||||
public set(name: string, text: string): void {
|
||||
this._terminalService.terminal.writeVerboseLine(`set help text "${name}" to "${text}"`);
|
||||
this.helpTextMap.set(name, text);
|
||||
}
|
||||
}
|
|
@ -30,6 +30,20 @@ export class SparoProfileService {
|
|||
@inject(LocalState) private _localState!: LocalState;
|
||||
@inject(GitSparseCheckoutService) private _gitSparseCheckoutService!: GitSparseCheckoutService;
|
||||
|
||||
/**
|
||||
* This function is used for completion
|
||||
*/
|
||||
public loadProfileNames(): string[] {
|
||||
const sparoProfileFolder: string = this._sparoProfileFolder;
|
||||
const sparoProfilePaths: string[] = FileSystem.readFolderItemNames(sparoProfileFolder, {
|
||||
absolutePaths: true
|
||||
});
|
||||
return sparoProfilePaths
|
||||
.filter((profilePath) => profilePath.endsWith('.json'))
|
||||
.map((profilePath) => SparoProfileService._getProfileName(profilePath))
|
||||
.sort();
|
||||
}
|
||||
|
||||
public async loadProfilesAsync(): Promise<void> {
|
||||
if (!this._loadPromise) {
|
||||
this._loadPromise = (async () => {
|
||||
|
|
19
build-tests/sparo-completion-test/.eslintrc.js
Normal file
19
build-tests/sparo-completion-test/.eslintrc.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
// This is a workaround for https://github.com/eslint/eslint/issues/3458
|
||||
require('@rushstack/heft-node-rig/profiles/default/includes/eslint/patch/modern-module-resolution');
|
||||
// This is a workaround for https://github.com/microsoft/rushstack/issues/3021
|
||||
require('@rushstack/heft-node-rig/profiles/default/includes/eslint/patch/custom-config-package-names');
|
||||
|
||||
module.exports = {
|
||||
extends: [
|
||||
'@rushstack/heft-node-rig/profiles/default/includes/eslint/profile/node-trusted-tool',
|
||||
'@rushstack/heft-node-rig/profiles/default/includes/eslint/mixins/friendly-locals'
|
||||
],
|
||||
parserOptions: { tsconfigRootDir: __dirname },
|
||||
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts', '*.tsx'],
|
||||
rules: {}
|
||||
}
|
||||
]
|
||||
};
|
21
build-tests/sparo-completion-test/LICENSE
Normal file
21
build-tests/sparo-completion-test/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) TikTok Pte. Ltd.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
11
build-tests/sparo-completion-test/README.md
Normal file
11
build-tests/sparo-completion-test/README.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# sparo-completion-test
|
||||
|
||||
Building this project tests sparo command completion outputs
|
||||
|
||||
# Details
|
||||
|
||||
`lib/start-test.js` is run after building the project. This scripts generate the output text files under `temp/etc`. In local builds, those files are copied to `etc` folder. During a CI build, the files under these two folders are compared and the CI build fails if they are different. This ensures that files under `etc` folder must be up to date in the PR, and people who review the PR must approve any changes.
|
||||
|
||||
# How to fix the build errors
|
||||
|
||||
Run `rush build -t sparo-completion-test` to regenerate files under `etc` folder and commit them into Git.
|
0
build-tests/sparo-completion-test/__fixture__/file.txt
Normal file
0
build-tests/sparo-completion-test/__fixture__/file.txt
Normal file
24
build-tests/sparo-completion-test/config/heft.json
Normal file
24
build-tests/sparo-completion-test/config/heft.json
Normal file
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/heft/v0/heft.schema.json",
|
||||
|
||||
"extends": "@rushstack/heft-node-rig/profiles/default/config/heft.json",
|
||||
|
||||
"phasesByName": {
|
||||
"build": {
|
||||
"tasksByName": {
|
||||
"post-compile": {
|
||||
"taskDependencies": ["typescript"],
|
||||
|
||||
"taskPlugin": {
|
||||
"pluginName": "run-script-plugin",
|
||||
"pluginPackage": "@rushstack/heft",
|
||||
|
||||
"options": {
|
||||
"scriptPath": "lib/start-test.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
5
build-tests/sparo-completion-test/config/rig.json
Normal file
5
build-tests/sparo-completion-test/config/rig.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
|
||||
"rigPackageName": "@rushstack/heft-node-rig"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
Running "sparo --get-yargs-completions sparo add __fixture__/":
|
||||
__fixture__/dir-a/
|
||||
__fixture__/file.txt
|
|
@ -0,0 +1,2 @@
|
|||
Running "sparo --get-yargs-completions sparo add __fixture__/dir-a/file":
|
||||
__fixture__/dir-a/file-under-a.txt
|
|
@ -0,0 +1,2 @@
|
|||
Running "sparo --get-yargs-completions sparo add __fixture__":
|
||||
__fixture__/
|
|
@ -0,0 +1 @@
|
|||
Running "sparo --get-yargs-completions sparo auto-config":
|
|
@ -0,0 +1,2 @@
|
|||
Running "sparo --get-yargs-completions sparo auto-config --":
|
||||
--overwrite
|
|
@ -0,0 +1,4 @@
|
|||
Running "sparo --get-yargs-completions sparo checkout --add-profile":
|
||||
my-team
|
||||
sparo-development
|
||||
sparo-website
|
|
@ -0,0 +1,3 @@
|
|||
Running "sparo --get-yargs-completions sparo checkout --add-profile spa":
|
||||
sparo-development
|
||||
sparo-website
|
|
@ -0,0 +1 @@
|
|||
Running "sparo --get-yargs-completions sparo checkout":
|
|
@ -0,0 +1,2 @@
|
|||
Running "sparo --get-yargs-completions sparo checkout --from web":
|
||||
website
|
|
@ -0,0 +1,6 @@
|
|||
Running "sparo --get-yargs-completions sparo checkout --":
|
||||
--no-profile
|
||||
--profile
|
||||
--add-profile
|
||||
--to
|
||||
--from
|
|
@ -0,0 +1,3 @@
|
|||
Running "sparo --get-yargs-completions sparo checkout --no-profile --":
|
||||
--to
|
||||
--from
|
|
@ -0,0 +1,2 @@
|
|||
Running "sparo --get-yargs-completions sparo checkout --pro":
|
||||
--profile
|
|
@ -0,0 +1,4 @@
|
|||
Running "sparo --get-yargs-completions sparo checkout --profile":
|
||||
my-team
|
||||
sparo-development
|
||||
sparo-website
|
|
@ -0,0 +1,3 @@
|
|||
Running "sparo --get-yargs-completions sparo checkout --profile spa":
|
||||
sparo-development
|
||||
sparo-website
|
|
@ -0,0 +1,3 @@
|
|||
Running "sparo --get-yargs-completions sparo checkout --add-profile sparo-website --add-profile":
|
||||
my-team
|
||||
sparo-development
|
|
@ -0,0 +1,3 @@
|
|||
Running "sparo --get-yargs-completions sparo checkout --profile sparo-website --profile":
|
||||
my-team
|
||||
sparo-development
|
|
@ -0,0 +1,2 @@
|
|||
Running "sparo --get-yargs-completions sparo checkout --to web":
|
||||
website
|
|
@ -0,0 +1 @@
|
|||
Running "sparo --get-yargs-completions sparo clone":
|
|
@ -0,0 +1,5 @@
|
|||
Running "sparo --get-yargs-completions sparo clone --":
|
||||
--profile
|
||||
--branch
|
||||
--full
|
||||
--skip-git-config
|
|
@ -0,0 +1,2 @@
|
|||
Running "sparo --get-yargs-completions sparo clone --pro":
|
||||
--profile
|
|
@ -0,0 +1,2 @@
|
|||
Running "sparo --get-yargs-completions sparo clone --profile":
|
||||
--profile
|
|
@ -0,0 +1,2 @@
|
|||
Running "sparo --get-yargs-completions sparo fetch --al":
|
||||
--all
|
|
@ -0,0 +1,2 @@
|
|||
Running "sparo --get-yargs-completions sparo fetch":
|
||||
origin
|
|
@ -0,0 +1,3 @@
|
|||
Running "sparo --get-yargs-completions sparo fetch --":
|
||||
--all
|
||||
--tags
|
|
@ -0,0 +1,2 @@
|
|||
Running "sparo --get-yargs-completions sparo fetch --all --":
|
||||
--tags
|
|
@ -0,0 +1,2 @@
|
|||
Running "sparo --get-yargs-completions sparo fetch --tags --":
|
||||
--all
|
|
@ -0,0 +1,2 @@
|
|||
Running "sparo --get-yargs-completions sparo fetch origin HEA":
|
||||
HEAD
|
|
@ -0,0 +1 @@
|
|||
Running "sparo --get-yargs-completions sparo init-profile":
|
|
@ -0,0 +1,2 @@
|
|||
Running "sparo --get-yargs-completions sparo init-profile --":
|
||||
--profile
|
|
@ -0,0 +1,2 @@
|
|||
Running "sparo --get-yargs-completions sparo init-profile --pro":
|
||||
--profile
|
|
@ -0,0 +1 @@
|
|||
Running "sparo --get-yargs-completions sparo list-profiles":
|
|
@ -0,0 +1,2 @@
|
|||
Running "sparo --get-yargs-completions sparo list-profiles --":
|
||||
--project
|
|
@ -0,0 +1,2 @@
|
|||
Running "sparo --get-yargs-completions sparo list-profiles --pro":
|
||||
--project
|
|
@ -0,0 +1,2 @@
|
|||
Running "sparo --get-yargs-completions sparo list-profiles --project web":
|
||||
website
|
|
@ -0,0 +1,2 @@
|
|||
Running "sparo --get-yargs-completions sparo pull":
|
||||
origin
|
|
@ -0,0 +1,3 @@
|
|||
Running "sparo --get-yargs-completions sparo pull --":
|
||||
--profile
|
||||
--no-profile
|
|
@ -0,0 +1 @@
|
|||
Running "sparo --get-yargs-completions sparo pull --no-profile --":
|
|
@ -0,0 +1,2 @@
|
|||
Running "sparo --get-yargs-completions sparo pull --pro":
|
||||
--profile
|
|
@ -0,0 +1,4 @@
|
|||
Running "sparo --get-yargs-completions sparo pull --profile":
|
||||
my-team
|
||||
sparo-development
|
||||
sparo-website
|
|
@ -0,0 +1,3 @@
|
|||
Running "sparo --get-yargs-completions sparo pull --profile spa":
|
||||
sparo-development
|
||||
sparo-website
|
|
@ -0,0 +1,3 @@
|
|||
Running "sparo --get-yargs-completions sparo pull --profile sparo-website --profile":
|
||||
my-team
|
||||
sparo-development
|
|
@ -0,0 +1,5 @@
|
|||
Running "sparo --get-yargs-completions sparo rebase --":
|
||||
--continue
|
||||
--skip
|
||||
--abort
|
||||
--interactive
|
|
@ -0,0 +1,4 @@
|
|||
Running "sparo --get-yargs-completions sparo rebase --abort --":
|
||||
--continue
|
||||
--skip
|
||||
--interactive
|
|
@ -0,0 +1,4 @@
|
|||
Running "sparo --get-yargs-completions sparo rebase --continue --":
|
||||
--skip
|
||||
--abort
|
||||
--interactive
|
|
@ -0,0 +1,4 @@
|
|||
Running "sparo --get-yargs-completions sparo rebase -i --":
|
||||
--continue
|
||||
--skip
|
||||
--abort
|
|
@ -0,0 +1,4 @@
|
|||
Running "sparo --get-yargs-completions sparo rebase --interactive --":
|
||||
--continue
|
||||
--skip
|
||||
--abort
|
|
@ -0,0 +1,4 @@
|
|||
Running "sparo --get-yargs-completions sparo rebase --skip --":
|
||||
--continue
|
||||
--abort
|
||||
--interactive
|
|
@ -0,0 +1,2 @@
|
|||
Running "sparo --get-yargs-completions sparo rebase -":
|
||||
-i
|
|
@ -0,0 +1 @@
|
|||
Running "sparo --get-yargs-completions sparo rebase -i -":
|
|
@ -0,0 +1 @@
|
|||
Running "sparo --get-yargs-completions sparo rebase --interactive -":
|
|
@ -0,0 +1,23 @@
|
|||
Running "sparo --get-yargs-completions sparo":
|
||||
auto-config:Automatic setup optimized git config
|
||||
list-profiles:List all available profiles or query profiles that contain the specified project name
|
||||
init-profile:Initialize a new profile.
|
||||
clone:Clone a repository into a new directory
|
||||
checkout:Updates files in the working tree to match the version in the index or the specified tree. If no pathspec was given, git checkout will also update HEAD to set the specified branch as the current branch.
|
||||
fetch:fetch remote branch to local
|
||||
pull:Incorporates changes from a remote repository into the current branch.
|
||||
git-clone:original git clone command
|
||||
git-checkout:original git checkout command
|
||||
git-fetch:original git fetch command
|
||||
git-pull:original git pull command
|
||||
add:add file contents to the index
|
||||
branch:list, create, or delete branches
|
||||
commit:record changes to the repository
|
||||
diff:show changes between commits, commit and working tree, etc
|
||||
log:show commit logs
|
||||
merge:join two or more development histories together
|
||||
push:update remote refs along with associated objects
|
||||
rebase:forward-port local commits to the updated upstream head
|
||||
reset:reset current HEAD to the specified state
|
||||
restore:restore working tree files
|
||||
status:show the working tree status
|
24
build-tests/sparo-completion-test/package.json
Normal file
24
build-tests/sparo-completion-test/package.json
Normal file
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"name": "sparo-completion-test",
|
||||
"description": "Building this project tests Sparo command-line outputs",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"_phase:build": "heft run --only build -- --clean",
|
||||
"build": "heft build --clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@rushstack/node-core-library": "~3.64.2",
|
||||
"build-test-utilities": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rushstack/heft": "0.64.3",
|
||||
"@rushstack/heft-node-rig": "2.4.5",
|
||||
"@types/heft-jest": "1.0.6",
|
||||
"@types/node": "20.11.16",
|
||||
"eslint": "8.56.0",
|
||||
"typescript": "~5.3.3"
|
||||
},
|
||||
"keywords": [],
|
||||
"license": "MIT"
|
||||
}
|
318
build-tests/sparo-completion-test/src/start-test.ts
Normal file
318
build-tests/sparo-completion-test/src/start-test.ts
Normal file
|
@ -0,0 +1,318 @@
|
|||
import {
|
||||
ICommandDefinition,
|
||||
executeCommandsAndCollectOutputs,
|
||||
updateOrCompareOutputs
|
||||
} from 'build-test-utilities';
|
||||
import type { IRunScriptOptions } from '@rushstack/heft';
|
||||
|
||||
/**
|
||||
* This build test is highly inspired by the build test for api-extractor in rushstack.
|
||||
*/
|
||||
export async function runAsync(runScriptOptions: IRunScriptOptions): Promise<void> {
|
||||
const {
|
||||
heftTaskSession: {
|
||||
logger,
|
||||
parameters: { production }
|
||||
},
|
||||
heftConfiguration: { buildFolderPath }
|
||||
} = runScriptOptions;
|
||||
|
||||
const prefixArgs: string[] = ['--get-yargs-completions', 'sparo'];
|
||||
|
||||
const commandDefinitions: ICommandDefinition[] = [
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'sparo-top-level-completion',
|
||||
args: prefixArgs.concat([])
|
||||
},
|
||||
// auto-config
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'auto-config-completion',
|
||||
args: prefixArgs.concat(['auto-config'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'auto-config-long-parameters-completion',
|
||||
args: prefixArgs.concat(['auto-config', '--'])
|
||||
},
|
||||
// list-profiles
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'list-profiles-completion',
|
||||
args: prefixArgs.concat(['list-profiles'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'list-profiles-long-parameters-completion',
|
||||
args: prefixArgs.concat(['list-profiles', '--'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'list-profiles-pro-completion',
|
||||
args: prefixArgs.concat(['list-profiles', '--pro'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'list-profiles-project-web-completion',
|
||||
args: prefixArgs.concat(['list-profiles', '--project', 'web'])
|
||||
},
|
||||
// init-profile
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'init-profile-completion',
|
||||
args: prefixArgs.concat(['init-profile'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'init-profile-long-parameters-completion',
|
||||
args: prefixArgs.concat(['init-profile', '--'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'init-profile-pro-completion',
|
||||
args: prefixArgs.concat(['init-profile', '--pro'])
|
||||
},
|
||||
// clone
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'clone-completion',
|
||||
args: prefixArgs.concat(['clone'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'clone-long-parameters-completion',
|
||||
args: prefixArgs.concat(['clone', '--'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'clone-long-parameters-completion',
|
||||
args: prefixArgs.concat(['clone', '--'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'clone-pro-completion',
|
||||
args: prefixArgs.concat(['clone', '--pro'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'clone-profile-completion',
|
||||
args: prefixArgs.concat(['clone', '--profile'])
|
||||
},
|
||||
// checkout
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'checkout-completion',
|
||||
args: prefixArgs.concat(['checkout'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'checkout-long-parameters-completion',
|
||||
args: prefixArgs.concat(['checkout', '--'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'checkout-pro-completion',
|
||||
args: prefixArgs.concat(['checkout', '--pro'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'checkout-profile-completion',
|
||||
args: prefixArgs.concat(['checkout', '--profile'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'checkout-profile-spa-completion',
|
||||
args: prefixArgs.concat(['checkout', '--profile', 'spa'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'checkout-second-profile-completion',
|
||||
args: prefixArgs.concat(['checkout', '--profile', 'sparo-website', '--profile'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'checkout-add-profile-completion',
|
||||
args: prefixArgs.concat(['checkout', '--add-profile'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'checkout-add-profile-spa-completion',
|
||||
args: prefixArgs.concat(['checkout', '--add-profile', 'spa'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'checkout-second-add-profile-completion',
|
||||
args: prefixArgs.concat(['checkout', '--add-profile', 'sparo-website', '--add-profile'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'checkout-no-profile-completion',
|
||||
args: prefixArgs.concat(['checkout', '--no-profile', '--'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'checkout-to-web-completion',
|
||||
args: prefixArgs.concat(['checkout', '--to', 'web'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'checkout-from-web-completion',
|
||||
args: prefixArgs.concat(['checkout', '--from', 'web'])
|
||||
},
|
||||
// fetch
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'fetch-completion',
|
||||
args: prefixArgs.concat(['fetch'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'fetch-long-parameters-completion',
|
||||
args: prefixArgs.concat(['fetch', '--'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'fetch-long-parameters-with-all-completion',
|
||||
args: prefixArgs.concat(['fetch', '--all', '--'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'fetch-long-parameters-with-tags-completion',
|
||||
args: prefixArgs.concat(['fetch', '--tags', '--'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'fetch-al-completion',
|
||||
args: prefixArgs.concat(['fetch', '--al'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'fetch-origin-HEA-completion',
|
||||
args: prefixArgs.concat(['fetch', 'origin', 'HEA'])
|
||||
},
|
||||
// pull
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'pull-completion',
|
||||
args: prefixArgs.concat(['pull'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'pull-long-parameters-completion',
|
||||
args: prefixArgs.concat(['pull', '--'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'pull-pro-completion',
|
||||
args: prefixArgs.concat(['pull', '--pro'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'pull-profile-completion',
|
||||
args: prefixArgs.concat(['pull', '--profile'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'pull-profile-spa-completion',
|
||||
args: prefixArgs.concat(['pull', '--profile', 'spa'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'pull-second-profile-completion',
|
||||
args: prefixArgs.concat(['pull', '--profile', 'sparo-website', '--profile'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'pull-no-profile-completion',
|
||||
args: prefixArgs.concat(['pull', '--no-profile', '--'])
|
||||
},
|
||||
// add
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'add-partial-completion',
|
||||
args: prefixArgs.concat(['add', '__fixture__']),
|
||||
processStdout: replaceBackslashes
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'add-folder-completion',
|
||||
args: prefixArgs.concat(['add', '__fixture__/']),
|
||||
processStdout: replaceBackslashes
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'add-folder-partial-completion',
|
||||
args: prefixArgs.concat(['add', '__fixture__/dir-a/file']),
|
||||
processStdout: replaceBackslashes
|
||||
},
|
||||
// branch
|
||||
// commit
|
||||
// diff
|
||||
// log
|
||||
// merge
|
||||
// rebase
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'rebase-short-parameters-completion',
|
||||
args: prefixArgs.concat(['rebase', '-'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'rebase-short-with-i-completion',
|
||||
args: prefixArgs.concat(['rebase', '-i', '-'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'rebase-short-with-interactive-completion',
|
||||
args: prefixArgs.concat(['rebase', '--interactive', '-'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'rebase-long-parameters-completion',
|
||||
args: prefixArgs.concat(['rebase', '--'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'rebase-long-with-continue-completion',
|
||||
args: prefixArgs.concat(['rebase', '--continue', '--'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'rebase-long-with-skip-completion',
|
||||
args: prefixArgs.concat(['rebase', '--skip', '--'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'rebase-long-with-abort-completion',
|
||||
args: prefixArgs.concat(['rebase', '--abort', '--'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'rebase-long-with-interactive-completion',
|
||||
args: prefixArgs.concat(['rebase', '--interactive', '--'])
|
||||
},
|
||||
{
|
||||
kind: 'sparo-command',
|
||||
name: 'rebase-long-with-i-completion',
|
||||
args: prefixArgs.concat(['rebase', '-i', '--'])
|
||||
}
|
||||
// restore
|
||||
// status
|
||||
];
|
||||
|
||||
await executeCommandsAndCollectOutputs({
|
||||
buildFolderPath,
|
||||
commandDefinitions
|
||||
});
|
||||
|
||||
await updateOrCompareOutputs({
|
||||
buildFolderPath,
|
||||
logger,
|
||||
production
|
||||
});
|
||||
}
|
||||
|
||||
function replaceBackslashes(text: string): string {
|
||||
return text.replace(/\\/g, '/');
|
||||
}
|
8
build-tests/sparo-completion-test/tsconfig.json
Normal file
8
build-tests/sparo-completion-test/tsconfig.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"$schema": "http://json.schemastore.org/tsconfig",
|
||||
|
||||
"extends": "./node_modules/@rushstack/heft-node-rig/profiles/default/tsconfig-base.json",
|
||||
"compilerOptions": {
|
||||
"types": ["node", "heft-jest"]
|
||||
}
|
||||
}
|
|
@ -6,6 +6,8 @@ Git version is __VERSION__
|
|||
|
||||
sparo clone <repository> [directory]
|
||||
|
||||
Clone a repository into a new directory
|
||||
|
||||
Positionals:
|
||||
repository The remote repository to clone from. [string] [required]
|
||||
directory The name of a new directory to clone into. The "humanish" part of
|
||||
|
|
|
@ -12,7 +12,7 @@ Commands:
|
|||
profiles that contain the specified
|
||||
project name
|
||||
sparo init-profile Initialize a new profile.
|
||||
sparo clone <repository> [directory]
|
||||
sparo clone <repository> [directory] Clone a repository into a new directory
|
||||
sparo checkout [branch] [start-point] Updates files in the working tree to
|
||||
match the version in the index or the
|
||||
specified tree. If no pathspec was
|
||||
|
|
|
@ -27,6 +27,11 @@ export interface ISparoCommandDefinition {
|
|||
* The working directory
|
||||
*/
|
||||
currentWorkingDirectory?: string;
|
||||
|
||||
/**
|
||||
* Process stdout. Use case: Unify path separator to /
|
||||
*/
|
||||
processStdout?: (output: string) => string;
|
||||
}
|
||||
|
||||
export interface ICustomCallbackDefinition {
|
||||
|
@ -64,7 +69,7 @@ export async function executeCommandsAndCollectOutputs({
|
|||
const { kind } = commandListDefinition;
|
||||
switch (commandListDefinition.kind) {
|
||||
case 'sparo-command': {
|
||||
const { name, args, currentWorkingDirectory } = commandListDefinition;
|
||||
const { name, args, currentWorkingDirectory, processStdout } = commandListDefinition;
|
||||
const subProcess: ChildProcess = Executable.spawn(sparoBinPath, args, {
|
||||
stdio: 'pipe',
|
||||
currentWorkingDirectory,
|
||||
|
@ -78,8 +83,11 @@ export async function executeCommandsAndCollectOutputs({
|
|||
let stdout: string = '';
|
||||
let stderr: string = '';
|
||||
subProcess.stdout?.on('data', (data: Buffer) => {
|
||||
const text: string = data.toString();
|
||||
let text: string = data.toString();
|
||||
console.log(text);
|
||||
if (processStdout) {
|
||||
text = processStdout(text);
|
||||
}
|
||||
stdout += text;
|
||||
});
|
||||
|
||||
|
@ -111,6 +119,7 @@ export async function executeCommandsAndCollectOutputs({
|
|||
outputPath,
|
||||
`Running "sparo ${args.join(' ')}":\n${processSparoOutput(
|
||||
stdout,
|
||||
// process.cwd() -> project folder
|
||||
currentWorkingDirectory || process.cwd()
|
||||
)}`
|
||||
);
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"changes": [
|
||||
{
|
||||
"packageName": "sparo",
|
||||
"comment": "Supports shell completion",
|
||||
"type": "none"
|
||||
}
|
||||
],
|
||||
"packageName": "sparo"
|
||||
}
|
|
@ -149,6 +149,34 @@ importers:
|
|||
specifier: ~5.3.3
|
||||
version: 5.3.3
|
||||
|
||||
../../build-tests/sparo-completion-test:
|
||||
dependencies:
|
||||
'@rushstack/node-core-library':
|
||||
specifier: ~3.64.2
|
||||
version: 3.64.2(@types/node@20.11.16)
|
||||
build-test-utilities:
|
||||
specifier: workspace:*
|
||||
version: link:../test-utilities
|
||||
devDependencies:
|
||||
'@rushstack/heft':
|
||||
specifier: 0.64.3
|
||||
version: 0.64.3(@types/node@20.11.16)
|
||||
'@rushstack/heft-node-rig':
|
||||
specifier: 2.4.5
|
||||
version: 2.4.5(@rushstack/heft@0.64.3)(@types/node@20.11.16)
|
||||
'@types/heft-jest':
|
||||
specifier: 1.0.6
|
||||
version: 1.0.6
|
||||
'@types/node':
|
||||
specifier: 20.11.16
|
||||
version: 20.11.16
|
||||
eslint:
|
||||
specifier: 8.56.0
|
||||
version: 8.56.0
|
||||
typescript:
|
||||
specifier: ~5.3.3
|
||||
version: 5.3.3
|
||||
|
||||
../../build-tests/sparo-output-test:
|
||||
dependencies:
|
||||
'@rushstack/node-core-library':
|
||||
|
|
|
@ -13,5 +13,6 @@ words:
|
|||
- rushstack
|
||||
- Sparo
|
||||
- tiktok
|
||||
- yargs
|
||||
ignoreWords: []
|
||||
import: []
|
||||
|
|
35
rush.json
35
rush.json
|
@ -270,37 +270,6 @@
|
|||
"postRushx": []
|
||||
},
|
||||
|
||||
/**
|
||||
* Installation variants allow you to maintain a parallel set of configuration files that can be
|
||||
* used to build the entire monorepo with an alternate set of dependencies. For example, suppose
|
||||
* you upgrade all your projects to use a new release of an important framework, but during a transition period
|
||||
* you intend to maintain compatibility with the old release. In this situation, you probably want your
|
||||
* CI validation to build the entire repo twice: once with the old release, and once with the new release.
|
||||
*
|
||||
* Rush "installation variants" correspond to sets of config files located under this folder:
|
||||
*
|
||||
* common/config/rush/variants/<variant_name>
|
||||
*
|
||||
* The variant folder can contain an alternate common-versions.json file. Its "preferredVersions" field can be used
|
||||
* to select older versions of dependencies (within a loose SemVer range specified in your package.json files).
|
||||
* To install a variant, run "rush install --variant <variant_name>".
|
||||
*
|
||||
* For more details and instructions, see this article: https://rushjs.io/pages/advanced/installation_variants/
|
||||
*/
|
||||
"variants": [
|
||||
// {
|
||||
// /**
|
||||
// * The folder name for this variant.
|
||||
// */
|
||||
// "variantName": "old-sdk",
|
||||
//
|
||||
// /**
|
||||
// * An informative description
|
||||
// */
|
||||
// "description": "Build this repo using the previous release of the SDK"
|
||||
// }
|
||||
],
|
||||
|
||||
/**
|
||||
* Rush can collect anonymous telemetry about everyday developer activity such as
|
||||
* success/failure of installs, builds, and other operations. You can use this to identify
|
||||
|
@ -436,6 +405,10 @@
|
|||
// }
|
||||
|
||||
// Build tests
|
||||
{
|
||||
"packageName": "sparo-completion-test",
|
||||
"projectFolder": "build-tests/sparo-completion-test"
|
||||
},
|
||||
{
|
||||
"packageName": "sparo-output-test",
|
||||
"projectFolder": "build-tests/sparo-output-test"
|
||||
|
|
Loading…
Reference in a new issue