diff --git a/.eslintrc.json b/.eslintrc.json index 3b9586024..907b53742 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -16,6 +16,9 @@ "es6": true, "jest/globals": true }, + "globals": { + "Deno": true + }, "rules": { // Error out for code formatting errors "prettier/prettier": "error", @@ -55,6 +58,8 @@ // For this project only use kebab-case "unicorn/filename-case": ["error", { "cases": { "kebabCase": true } }], // Allow Array.from(set) mitigate TS2569 which would require '--downlevelIteration' - "unicorn/prefer-spread": "off" + "unicorn/prefer-spread": "off", + // Temporarily allow disabling for whole file + "eslint-comments/no-use": "off" } } diff --git a/action.yml b/action.yml index 1e0e6a95e..20429d76a 100644 --- a/action.yml +++ b/action.yml @@ -1,6 +1,58 @@ name: 'Unity - Builder' author: Webber Takken description: 'Build Unity projects for different platforms.' +runs: + using: 'composite' + steps: + - run: echo "Using GameCI CLI to build project" + shell: bash + - uses: denoland/setup-deno@v1 + with: + deno-version: v1.x + - run: | + deno run --allow-run ./cli/index.ts build \ + --targetPlatform="${{ inputs.targetPlatform }}" \ + --unityVersion="${{ inputs.unityVersion }}" \ + --customImage="${{ inputs.customImage }}" \ + --projectPath="${{ inputs.projectPath }}" \ + --buildName="${{ inputs.buildName }}" \ + --buildsPath="${{ inputs.buildsPath }}" \ + --buildMethod="${{ inputs.buildMethod }}" \ + --customParameters="${{ inputs.customParameters }}" \ + --versioning="${{ inputs.versioning }}" \ + --version="${{ inputs.version }}" \ + --androidVersionCode="${{ inputs.androidVersionCode }}" \ + --androidAppBundle="${{ inputs.androidAppBundle }}" \ + --androidKeystoreName="${{ inputs.androidKeystoreName }}" \ + --androidKeystoreBase64="${{ inputs.androidKeystoreBase64 }}" \ + --androidKeystorePass="${{ inputs.androidKeystorePass }}" \ + --androidKeyaliasName="${{ inputs.androidKeyaliasName }}" \ + --androidKeyaliasPass="${{ inputs.androidKeyaliasPass }}" \ + --androidTargetSdkVersion="${{ inputs.androidTargetSdkVersion }}" \ + --sshAgent="${{ inputs.sshAgent }}" \ + --gitPrivateToken="${{ inputs.gitPrivateToken }}" \ + --chownFilesTo="${{ inputs.chownFilesTo }}" \ + --allowDirtyBuild="${{ inputs.allowDirtyBuild }}" \ + --postBuildSteps="${{ inputs.postBuildSteps }}" \ + --preBuildSteps="${{ inputs.preBuildSteps }}" \ + --customJobHooks="${{ inputs.customJobHooks }}" \ + --customJob="${{ inputs.customJob }}" \ + --awsBaseStackName="${{ inputs.awsBaseStackName }}" \ + --cloudRunnerCluster="${{ inputs.cloudRunnerCluster }}" \ + --cloudRunnerCpu="${{ inputs.cloudRunnerCpu }}" \ + --cloudRunnerMemory="${{ inputs.cloudRunnerMemory }}" \ + --cachePushOverrideCommand="${{ inputs.cachePushOverrideCommand }}" \ + --cachePullOverrideCommand="${{ inputs.cachePullOverrideCommand }}" \ + --readInputFromOverrideList="${{ inputs.readInputFromOverrideList }}" \ + --readInputOverrideCommand="${{ inputs.readInputOverrideCommand }}" \ + --kubeConfig="${{ inputs.kubeConfig }}" \ + --kubeVolume="${{ inputs.kubeVolume }}" \ + --kubeStorageClass="${{ inputs.kubeStorageClass }}" \ + --kubeVolumeSize="${{ inputs.kubeVolumeSize }}" \ + --cacheKey="${{ inputs.cacheKey }}" \ + --checkDependencyHealthOverride="${{ inputs.checkDependencyHealthOverride }}" \ + --startDependenciesOverride="${{ inputs.startDependenciesOverride }}" + shell: bash inputs: targetPlatform: required: true @@ -174,6 +226,3 @@ outputs: branding: icon: 'box' color: 'gray-dark' -runs: - using: 'node12' - main: 'dist/index.js' diff --git a/cli/arguments-parser/arguments-parser.ts b/cli/arguments-parser/arguments-parser.ts new file mode 100644 index 000000000..20c93e2b7 --- /dev/null +++ b/cli/arguments-parser/arguments-parser.ts @@ -0,0 +1,12 @@ +import { parseArgv } from '../core/parse-argv.ts'; + +export class ArgumentsParser { + static parse(cliArguments: string[]) { + const [commandName, ...arguments] = cliArguments; + + return { + commandName, + options: parseArgv(arguments), + }; + } +} diff --git a/cli/bootstrapper.ts b/cli/bootstrapper.ts new file mode 100644 index 000000000..a617c9438 --- /dev/null +++ b/cli/bootstrapper.ts @@ -0,0 +1,28 @@ +import { CommandFactory } from './commands/command-factory.ts'; +import { ArgumentsParser } from './arguments-parser/arguments-parser.ts'; +import { Options } from './config/options.ts'; +import { CommandInterface } from './commands/command/CommandInterface.ts'; + +export class Bootstrapper { + private readonly commandFactory: CommandFactory; + private readonly argumentsParser: ArgumentsParser; + + private options?: Options; + private command?: CommandInterface; + + constructor() { + this.commandFactory = new CommandFactory(); + this.argumentsParser = ArgumentsParser; + } + + public async run(cliArguments: string[]) { + const { commandName, options } = this.argumentsParser.parse(cliArguments); + + this.options = new Options(options); + this.command = this.commandFactory.createCommand(commandName); + + // Command agnostic stuff here + + await this.command.execute(this.options); + } +} diff --git a/cli/commands/command-factory.ts b/cli/commands/command-factory.ts new file mode 100644 index 000000000..65ab38e8b --- /dev/null +++ b/cli/commands/command-factory.ts @@ -0,0 +1,15 @@ +import { NonExistentCommand } from './command/non-existent-command.ts'; +import { BuildCommand } from './command/build-command.ts'; + +export class CommandFactory { + constructor() {} + + public createCommand(commandName) { + switch (commandName) { + case 'build': + return new BuildCommand(); + default: + return new NonExistentCommand(commandName); + } + } +} diff --git a/cli/commands/command/CommandInterface.ts b/cli/commands/command/CommandInterface.ts new file mode 100644 index 000000000..74689cc24 --- /dev/null +++ b/cli/commands/command/CommandInterface.ts @@ -0,0 +1,6 @@ +import { Options } from '../../config/options.ts'; + +export interface CommandInterface { + name: string; + execute: (options: Options) => Promise; +} diff --git a/cli/commands/command/build-command.ts b/cli/commands/command/build-command.ts new file mode 100644 index 000000000..2f8ab2cf5 --- /dev/null +++ b/cli/commands/command/build-command.ts @@ -0,0 +1,23 @@ +import { CommandInterface } from './CommandInterface.ts'; +import { exec, OutputMode } from 'https://deno.land/x/exec@0.0.5/mod.ts'; +import { Options } from '../../config/options.ts'; + +export class BuildCommand implements CommandInterface { + public readonly name: string; + + constructor(name: string) { + this.name = name; + } + + public async execute(options: Options) { + const result = await exec('docker run -it unityci/editor:2020.3.15f2-base-1 /bin/bash -c "echo test"', { + output: OutputMode.Capture, + continueOnError: true, + + // verbose: true, + }); + + console.log(options); + console.log(result.output); + } +} diff --git a/cli/commands/command/non-existent-command.ts b/cli/commands/command/non-existent-command.ts new file mode 100644 index 000000000..67ac00d7c --- /dev/null +++ b/cli/commands/command/non-existent-command.ts @@ -0,0 +1,14 @@ +import { CommandInterface } from './CommandInterface.ts'; +import { Options } from '../../config/options.ts'; + +export class NonExistentCommand implements CommandInterface { + public name: string; + + constructor(name: string) { + this.name = name; + } + + public async execute(options: Options) { + throw new Error(`Command ${this.name} does not exist`); + } +} diff --git a/cli/config/options.ts b/cli/config/options.ts new file mode 100644 index 000000000..d646edc5f --- /dev/null +++ b/cli/config/options.ts @@ -0,0 +1,9 @@ +import { CliOptions } from '../core/cli-options.ts'; + +export class Options { + public options: CliOptions; + + constructor(optionsFromCli) { + this.options = optionsFromCli; + } +} diff --git a/cli/core/cli-options.ts b/cli/core/cli-options.ts new file mode 100644 index 000000000..b099a31b7 --- /dev/null +++ b/cli/core/cli-options.ts @@ -0,0 +1 @@ +export type CliOptions = Map; diff --git a/cli/core/parse-argv.ts b/cli/core/parse-argv.ts new file mode 100644 index 000000000..4c0bfc698 --- /dev/null +++ b/cli/core/parse-argv.ts @@ -0,0 +1,51 @@ +import { CliOptions } from './cli-options.ts'; + +/** + * Parse command line arguments + * + * Usage: + * console.dir(parseArgv(Deno.args)); // Deno + * console.log(parseArgv(process.argv)); // Node + * + * Example: + * deno run my-script -test1=1 -test2 "2" -test3 -test4 false -test5 "one" -test6= -test7=9BX9 + * + * Output: + * Map { + * "test1" => 1, + * "test2" => 2, + * "test3" => true, + * "test4" => false, + * "test5" => "one", + * "test6" => "", + * "test7" => "9BX9" + * } + */ +export const parseArgv = (argv: string[] = [], { verbose = false } = {}): CliOptions => { + const providedArguments = new Map(); + + for (let current = 0, next = 1; current < argv.length; current += 1, next += 1) { + // Detect flag + if (!argv[current].startsWith('-')) continue; + let flag = argv[current].replace(/^-+/, ''); + + // Detect value + const hasNextArgument = next < argv.length && !argv[next].startsWith('-'); + let value: string | number | boolean = hasNextArgument ? argv[next] : 'true'; + + // Split combinations + const isCombination = flag.includes('='); + if (isCombination) [flag, value] = flag.split('='); + + // Parse types + if (['true', 'false'].includes(value)) value = value === 'true'; + else if (!Number.isNaN(Number(value)) && !Number.isNaN(Number.parseInt(value))) value = Number.parseInt(value); + + // Assign + // eslint-disable-next-line no-console + if (verbose) console.log(`Found flag "${flag}" with value "${value}" (${typeof value}).`); + providedArguments.set(flag, value); + } + + return providedArguments; +}; diff --git a/cli/index.ts b/cli/index.ts new file mode 100644 index 000000000..7a2b224b1 --- /dev/null +++ b/cli/index.ts @@ -0,0 +1,11 @@ +/* eslint-disable no-console */ +import { Bootstrapper } from './bootstrapper.ts'; + +(async () => { + try { + await new Bootstrapper().run(Deno.args); + } catch (error) { + console.error(error); + Deno.exit(1); + } +})(); diff --git a/deno.json b/deno.json new file mode 100644 index 000000000..fb4c2294b --- /dev/null +++ b/deno.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "allowJs": true, + "lib": ["deno.window"], + "strict": true + }, + "lint": { + "files": { + "include": ["cli/"], + "exclude": [] + }, + "rules": { + "tags": ["recommended"], + "include": ["ban-untagged-todo"], + "exclude": ["no-unused-vars"] + } + }, + "fmt": { + "files": { + "include": ["cli/"], + "exclude": [] + }, + "options": { + "useTabs": false, + "lineWidth": 120, + "indentWidth": 2, + "singleQuote": true, + "proseWrap": "preserve" + } + } +} diff --git a/tsconfig.json b/tsconfig.json index e8c9118c8..474bccee3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ "target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */, "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, "outDir": "./lib" /* Redirect output structure to the directory. */, - "rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, + "rootDir": "./cli" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, "strict": true /* Enable all strict type-checking options. */, "noImplicitAny": false /* Re-enable after fixing compatibility */ /* Raise error on expressions and declarations with an implied 'any' type. */, "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */