feat: engine-specific commands and better organised environment

pull/413/head
Webber 2022-08-13 02:51:26 +02:00
parent 18af761e9f
commit cae5ffaf6c
10 changed files with 125 additions and 50 deletions

View File

@ -1,11 +1,28 @@
import { NonExistentCommand } from './command/non-existent-command.ts'; import { NonExistentCommand } from './command/non-existent-command.ts';
import { BuildCommand } from './command/build-command.ts'; import { BuildCommand } from './command/unity/build-command.ts';
import { BuildRemoteCommand } from './command/build-remote-command.ts'; import { BuildRemoteCommand } from './command/unity/build-remote-command.ts';
import { CommandInterface } from './command/command-interface.ts';
export class CommandFactory { export class CommandFactory {
constructor() {} constructor() {}
public createCommand(commandName: string) { selectEngine(engine: string, engineVersion: string) {
this.engine = engine;
this.engineVersion = engineVersion;
return this;
}
public createCommand(commandName: string): CommandInterface {
switch (this.engine) {
case 'unity':
return this.createUnityCommand(commandName);
default:
throw new Error(`Engine ${this.engine} is not yet supported.`);
}
}
private createUnityCommand(commandName: string) {
switch (commandName) { switch (commandName) {
case 'build': case 'build':
return new BuildCommand(commandName); return new BuildCommand(commandName);

View File

@ -1,11 +1,11 @@
import { exec, OutputMode } from 'https://deno.land/x/exec@0.0.5/mod.ts'; import { exec, OutputMode } from 'https://deno.land/x/exec@0.0.5/mod.ts';
import { CommandInterface } from './command-interface.ts'; import { CommandInterface } from '../command-interface.ts';
import { Options } from '../../config/options.ts'; import { Options } from '../../../config/options.ts';
import { Action, Cache, CloudRunner, Docker, ImageTag, Input, Output } from '../../model/index.ts'; import { Action, Cache, CloudRunner, Docker, ImageTag, Input, Output } from '../../../model/index.ts';
import PlatformSetup from '../../model/platform-setup.ts'; import PlatformSetup from '../../../model/platform-setup.ts';
import { core, process } from '../../dependencies.ts'; import { core, process } from '../../../dependencies.ts';
import MacBuilder from '../../model/mac-builder.ts'; import MacBuilder from '../../../model/mac-builder.ts';
import Parameters from '../../model/parameters.ts'; import Parameters from '../../../model/parameters.ts';
export class BuildCommand implements CommandInterface { export class BuildCommand implements CommandInterface {
public readonly name: string; public readonly name: string;

View File

@ -1,8 +1,8 @@
import { CommandInterface } from './command-interface.ts'; import { CommandInterface } from '../command-interface.ts';
import { Options } from '../../config/options.ts'; import { Options } from '../../../config/options.ts';
import { CloudRunner, ImageTag, Input, Output } from '../../model/index.ts'; import { CloudRunner, ImageTag, Input, Output } from '../../../model/index.ts';
import { core } from '../../dependencies.ts'; import { core } from '../../../dependencies.ts';
import Parameters from '../../model/parameters.ts'; import Parameters from '../../../model/parameters.ts';
// Todo - Verify this entire flow // Todo - Verify this entire flow
export class BuildRemoteCommand implements CommandInterface { export class BuildRemoteCommand implements CommandInterface {

View File

@ -1,17 +1,15 @@
import { CliArguments } from '../core/cli/cli-arguments.ts'; import { CliArguments } from '../core/cli/cli-arguments.ts';
import { EnvVariables } from '../core/env/env-variables.ts';
import { Parameters, Input } from '../model/index.ts'; import { Parameters, Input } from '../model/index.ts';
import { CommandInterface } from '../commands/command/command-interface.ts'; import { CommandInterface } from '../commands/command/command-interface.ts';
import { Environment } from '../core/env/environment.ts';
export class Options { export class Options {
public input: Input; public input: Input;
public parameters: Parameters; public parameters: Parameters;
private readonly env: EnvVariables; private readonly env: Environment;
private readonly command: CommandInterface; private command: CommandInterface;
constructor(command: CommandInterface, env: EnvVariables) { constructor(command: CommandInterface, env: Environment) {
this.input = null;
this.parameters = null;
this.env = env; this.env = env;
this.command = command; this.command = command;
@ -26,4 +24,10 @@ export class Options {
return this; return this;
} }
registerCommand(command: CommandInterface) {
this.command = command;
return this;
}
} }

View File

@ -2,11 +2,13 @@ import { parseArgv } from './parse-argv.ts';
export class ArgumentsParser { export class ArgumentsParser {
public parse(cliArguments: string[]) { public parse(cliArguments: string[]) {
const [commandName, ...args] = cliArguments; const [commandName, ...rest] = cliArguments;
const { subCommands, args } = parseArgv(rest);
return { return {
commandName, commandName,
args: parseArgv(args), subCommands,
args,
}; };
} }
} }

View File

@ -8,9 +8,11 @@ import { CliArguments } from './cli-arguments.ts';
* console.log(parseArgv(process.argv)); // Node * console.log(parseArgv(process.argv)); // Node
* *
* Example: * Example:
* deno run my-script -test1=1 -test2 "2" -test3 -test4 false -test5 "one" -test6= -test7=9BX9 * deno run my-script my-project -test1=1 -test2 "2" -test3 -test4 false -test5 "one" -test6= -test7=9BX9
* *
* Output: * Output:
* [
* [ 'my-project' ],
* Map { * Map {
* "test1" => 1, * "test1" => 1,
* "test2" => 2, * "test2" => 2,
@ -20,11 +22,24 @@ import { CliArguments } from './cli-arguments.ts';
* "test6" => "", * "test6" => "",
* "test7" => "9BX9" * "test7" => "9BX9"
* } * }
* ]
*/ */
export const parseArgv = (argv: string[] = [], { verbose = false } = {}): CliArguments => { export const parseArgv = (argv: string[] = [], { verbose = false } = {}): CliArguments => {
const providedArguments = new Map<string, string | number | boolean>(); const subCommands: string[] = [];
const args = new Map<string, string | number | boolean>();
let hasParsedSubCommands = false;
for (let current = 0, next = 1; current < argv.length; current += 1, next += 1) { for (let current = 0, next = 1; current < argv.length; current += 1, next += 1) {
// Detect subCommands
if (!hasParsedSubCommands) {
if (argv[current].startsWith('-')) {
hasParsedSubCommands = true;
} else {
subCommands.push(argv[current]);
continue;
}
}
// Detect flag // Detect flag
if (!argv[current].startsWith('-')) continue; if (!argv[current].startsWith('-')) continue;
let flag = argv[current].replace(/^-+/, ''); let flag = argv[current].replace(/^-+/, '');
@ -44,8 +59,8 @@ export const parseArgv = (argv: string[] = [], { verbose = false } = {}): CliArg
// Assign // Assign
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
if (verbose) console.log(`Found flag "${flag}" with value "${value}" (${typeof value}).`); if (verbose) console.log(`Found flag "${flag}" with value "${value}" (${typeof value}).`);
providedArguments.set(flag, value); args.set(flag, value);
} }
return providedArguments; return { subCommands, args };
}; };

View File

@ -0,0 +1,15 @@
export class EngineDetector {
private projectPath: string;
constructor(subCommands: string[], args: string[]) {
this.projectPath = subCommands[0] || args.projectPath || '.';
}
public async detect(): Promise<{ engine: string; engineVersion: string }> {
// Todo - detect and return real versions
return {
engine: 'unity',
engineVersion: '2020.1.0f1',
};
}
}

29
src/core/env/environment.ts vendored 100644
View File

@ -0,0 +1,29 @@
type EnvVariables = { [index: string]: string };
export class Environment implements EnvVariables {
public readonly os: string;
public readonly arch: string;
constructor(env: Deno.env) {
// Make an immutable copy of the environment variables.
for (const [key, value] of Object.entries(env.toObject())) {
if (value !== undefined) this[key] = value;
}
// Override specific variables.
this.os = Deno.build.os;
this.arch = Deno.build.arch;
}
public get(key: string): string | undefined {
return this[key];
}
public getOS(): string {
return this.os;
}
public getArch(): string {
return this.arch;
}
}

View File

@ -1,34 +1,27 @@
import './core/logger/index.ts'; import './core/logger/index.ts';
import type { CommandInterface } from './commands/command/command-interface.ts';
import type { EnvVariables } from './core/env/env-variables.ts';
import { Options } from './config/options.ts'; import { Options } from './config/options.ts';
import { CommandFactory } from './commands/command-factory.ts'; import { CommandFactory } from './commands/command-factory.ts';
import { ArgumentsParser } from './core/cli/arguments-parser.ts'; import { ArgumentsParser } from './core/cli/arguments-parser.ts';
import System from './model/system.ts'; import { Environment } from './core/env/environment.ts';
import { EngineDetector } from './core/engine/engine-detector.ts';
export class GameCI { export class GameCI {
private readonly commandFactory: CommandFactory; private readonly env: Environment;
private readonly argumentsParser: ArgumentsParser;
private readonly env: EnvVariables;
private options?: Options; constructor() {
private command?: CommandInterface; this.env = new Environment(Deno.env);
this.args = Deno.args;
constructor(envVariables: EnvVariables) {
this.env = envVariables;
this.commandFactory = new CommandFactory();
this.argumentsParser = new ArgumentsParser();
} }
public async run(cliArguments: string[]) { public async run() {
try { try {
const { commandName, args } = this.argumentsParser.parse(cliArguments); const { commandName, subCommands, args } = new ArgumentsParser().parse(this.args);
const { engine, engineVersion } = await new EngineDetector(subCommands, args).detect();
this.options = await new Options(this.env).generateParameters(args); const command = new CommandFactory().selectEngine(engine, engineVersion).createCommand(commandName, subCommands);
this.command = this.commandFactory.createCommand(commandName); const options = await new Options(command, this.env).registerCommand(command).generateParameters(args);
await this.command.execute(this.options); await command.execute(options);
} catch (error) { } catch (error) {
log.error(error); log.error(error);
Deno.exit(1); Deno.exit(1);
@ -36,4 +29,4 @@ export class GameCI {
} }
} }
await new GameCI(Deno.env.toObject()).run(Deno.args); await new GameCI().run();

View File

@ -9,8 +9,8 @@ import Versioning from './versioning.ts';
import { GitRepoReader } from './input-readers/git-repo.ts'; import { GitRepoReader } from './input-readers/git-repo.ts';
import { GithubCliReader } from './input-readers/github-cli.ts'; import { GithubCliReader } from './input-readers/github-cli.ts';
import { Cli } from './cli/cli.ts'; import { Cli } from './cli/cli.ts';
import { EnvVariables } from '../core/env/env-variables.ts';
import { CommandInterface } from '../commands/command/command-interface.ts'; import { CommandInterface } from '../commands/command/command-interface.ts';
import { Environment } from '../core/env/environment.ts';
class Parameters { class Parameters {
private command: CommandInterface; private command: CommandInterface;
@ -69,9 +69,9 @@ class Parameters {
public isCliMode!: boolean; public isCliMode!: boolean;
private readonly input: Input; private readonly input: Input;
private readonly env: EnvVariables; private readonly env: Environment;
constructor(input: Input, env: EnvVariables) { constructor(input: Input, env: Environment) {
this.input = input; this.input = input;
this.env = env; this.env = env;
} }