Skip to content
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

Add internal HydeFronts automation scripts to lint and handle releases and versions #1360

Merged
merged 56 commits into from
Mar 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
d43b971
Create post-build.php
caendesilva Mar 27, 2023
4ce81ac
Include minified Minima
caendesilva Mar 27, 2023
1f0aca6
Sketch out main function
caendesilva Mar 27, 2023
b0d29e9
Create minima.php
caendesilva Mar 27, 2023
0d73670
Move include to added file
caendesilva Mar 27, 2023
f9f7e99
Make verify default and unwrap conditionals
caendesilva Mar 27, 2023
dc94f49
Get version from package.json
caendesilva Mar 27, 2023
0f57d88
Get the versions shown in stylesheets
caendesilva Mar 27, 2023
dcfcd3f
Reduce coupling between HydeRC test and HydeFront version
caendesilva Mar 27, 2023
cb0617a
Expect response to be file
caendesilva Mar 27, 2023
7a1bb33
Expect response to be file contents
caendesilva Mar 27, 2023
9ab2f85
Create lint.yml
caendesilva Mar 27, 2023
d29c500
Add HydeFront Lint job to monorepo continuous integration workflow
caendesilva Mar 27, 2023
f5722d1
Prefix relative filenames for output
caendesilva Mar 27, 2023
3b581f9
Validate the versions
caendesilva Mar 27, 2023
432f1c4
Buffer exit code so multiple linters can proceed to run
caendesilva Mar 27, 2023
15f673b
Exit with the returned exit code
caendesilva Mar 27, 2023
c205dcc
Format PHP
caendesilva Mar 27, 2023
2e59e83
Update output sentence style semantics
caendesilva Mar 27, 2023
a629b04
Implement option logic to automatically fix files
caendesilva Mar 27, 2023
2d8a6cb
Only run fixes for files needing to be fixed
caendesilva Mar 27, 2023
0be4f0e
Display a different message when there is nothing to fix
caendesilva Mar 27, 2023
6f5c5c1
Initialize exit code
caendesilva Mar 27, 2023
678afb8
Add newline
caendesilva Mar 27, 2023
8984c77
Add output depending on success state
caendesilva Mar 27, 2023
5133f0a
Create version.php
caendesilva Mar 27, 2023
e6ab15d
Default to return zero
caendesilva Mar 27, 2023
ef1fb2e
Validate script is run from monorepo root
caendesilva Mar 27, 2023
ad62481
Display the current working directory
caendesilva Mar 27, 2023
0f34444
Get and validate version
caendesilva Mar 27, 2023
ac5c5d8
Supress SpellCheckingInspection for statement
caendesilva Mar 27, 2023
8ba5aa9
Introduce local variable
caendesilva Mar 27, 2023
0c4008a
Implode versions
caendesilva Mar 27, 2023
ab706a9
Display output
caendesilva Mar 27, 2023
fd55929
Update local variable name
caendesilva Mar 27, 2023
daa2204
Run the NPM version command
caendesilva Mar 27, 2023
5bc7bc2
Add Json suffix
caendesilva Mar 27, 2023
d094146
Run the post-build script
caendesilva Mar 27, 2023
6bf95d3
Fail if called script fails
caendesilva Mar 27, 2023
081957b
Commit the changes
caendesilva Mar 27, 2023
cc5f453
Add finishing output
caendesilva Mar 27, 2023
b990da1
Add finishing reminder for tasks I want to be manual
caendesilva Mar 27, 2023
c21e5e9
Fix bad workflow syntax
caendesilva Mar 27, 2023
4579b93
Run command in the right directory
caendesilva Mar 27, 2023
39af69c
Fixes must be applied before validating versions
caendesilva Mar 27, 2023
49d9fec
Rerun verification script after running fixer
caendesilva Mar 27, 2023
c7b120f
Update commit message for monorepo commit
caendesilva Mar 27, 2023
a8b82fd
Add update reminder when not releasing a patch
caendesilva Mar 27, 2023
59ddc8f
Commit changes to monorepo before HydeFront
caendesilva Mar 27, 2023
e6fa162
Create HydeFront v3.3.1
caendesilva Mar 27, 2023
8d9d66f
Move down output
caendesilva Mar 27, 2023
bdd6b71
Reorder operations
caendesilva Mar 27, 2023
8f628fc
Add option to handle version injection
caendesilva Mar 27, 2023
cfd65af
Automatically inject the version when building for production
caendesilva Mar 27, 2023
e449db5
Copyright text is now handled at build time
caendesilva Mar 27, 2023
2f800b4
Create HydeFront v3.3.2
caendesilva Mar 27, 2023
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
17 changes: 13 additions & 4 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,6 @@ jobs:
working-directory: 'packages/hydefront'
run: npm run prod

- name: Add copyright text to compiled file
# add '/*! HydeFront v3.3.0 | MIT License | https://hydephp.com*/' to the top of the packages/hydefront/dist/hyde.css file
run: sed -i '1s/^/\/\*! HydeFront v3.3.0 | MIT License | https:\/\/hydephp.com\*\/\n/' packages/hydefront/dist/hyde.css

- name: Upload artifacts
uses: actions/upload-artifact@v1
with:
Expand Down Expand Up @@ -588,3 +584,16 @@ jobs:
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: snyk.sarif


hydefront-lint:
name: HydeFront Lint
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Run linter
run: php packages/hydefront/.github/scripts/post-build.php
4 changes: 4 additions & 0 deletions packages/hydefront/.github/scripts/minima.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?php

// This file is part of Minima.
interface Minima{const VERSION='v0.1.0-dev';}interface Console{const INPUT=STDIN;const OUTPUT=STDOUT;}interface ANSI extends ANSI_EXT,XML_ANSI{const BLACK="\033[30m";const RED="\033[31m";const GREEN="\033[32m";const YELLOW="\033[33m";const BLUE="\033[34m";const MAGENTA="\033[35m";const CYAN="\033[36m";const WHITE="\033[37m";const GRAY="\033[90m";const RESET="\033[0m";}interface ANSI_EXT{const BRIGHT_RED="\033[91m";const BRIGHT_GREEN="\033[92m";const BRIGHT_YELLOW="\033[93m";const BRIGHT_BLUE="\033[94m";const BRIGHT_MAGENTA="\033[95m";const BRIGHT_CYAN="\033[96m";const BRIGHT_WHITE="\033[97m";}interface XML_ANSI{const INFO=ANSI::GREEN;const WARNING=ANSI::YELLOW;const ERROR=ANSI::RED;const COMMENT=ANSI::GRAY;const RESET=ANSI::RESET;}trait InteractsWithIO{public function write(string $string):void{Output::write($string);}public function line(string $message=''):void{Output::write($message."\n");}public function info(string $message):void{$this->line(XML_ANSI::INFO.$message.ANSI::RESET);}public function warning(string $message):void{$this->line(XML_ANSI::WARNING.$message.ANSI::RESET);}public function error(string $message):void{$this->line(XML_ANSI::ERROR.$message.ANSI::RESET);}public function formatted(string $message,bool $newLine=true):void{$startTags=['<info>'=>XML_ANSI::INFO,'<warning>'=>XML_ANSI::WARNING,'<error>'=>XML_ANSI::ERROR,'<comment>'=>XML_ANSI::COMMENT,'<reset>'=>XML_ANSI::RESET,'<red>'=>ANSI::RED,'<green>'=>ANSI::GREEN,'<blue>'=>ANSI::BLUE,'<yellow>'=>ANSI::YELLOW,'<magenta>'=>ANSI::MAGENTA,'<cyan>'=>ANSI::CYAN,];$endTags=['</info>'=>XML_ANSI::RESET,'</warning>'=>XML_ANSI::RESET,'</error>'=>XML_ANSI::RESET,'</comment>'=>XML_ANSI::RESET,'</reset>'=>XML_ANSI::RESET,'</>'=>XML_ANSI::RESET,'</red>'=>ANSI::RESET,'</green>'=>ANSI::RESET,'</blue>'=>ANSI::RESET,'</yellow>'=>ANSI::RESET,'</magenta>'=>ANSI::RESET,'</cyan>'=>ANSI::RESET,];$formatted=str_replace(array_keys($startTags),array_values($startTags),$message);$formatted=str_replace(array_keys($endTags),array_values($endTags),$formatted);if($newLine){$this->line($formatted);}else{$this->write($formatted);}}public function ask(string $question,string $default=''):string{return Input::readline(ANSI::YELLOW."$question: ".ANSI::RESET)?:$default;}}trait AccessesArguments{protected function options():array{return $this->options;}protected function arguments():array{return $this->arguments;}protected function hasOption(string $name):bool{return isset($this->options[$name]);}protected function hasArgument(string $name):bool{return isset($this->arguments[$name])||isset(array_flip(array_values($this->arguments))[$name]);}protected function getOption(string $name,mixed $default=null):mixed{return $this->options[$name]?? $default;}protected function getArgument(string $name,mixed $default=null):mixed{return $this->arguments[$name]?? $this->getArgumentByValue($name)?? $default;}private function getArgumentByValue(string $value):?string{$index=array_flip(array_values($this->arguments))[$value]?? null;return $this->arguments[$index]?? null;}private static function parseOptions(array $options):array{$formatted=[];foreach($options as $index=>$option){$option=ltrim($option,'-');if(str_contains($option,'=')){$parts=explode('=',$option);$formatted[$parts[0]]=$parts[1];}else{$formatted[$option]=true;}}return $formatted;}private static function parseArguments(array $arguments):array{$formatted=[];foreach($arguments as $index=>$argument){if(str_contains($argument,'=')){$parts=explode('=',$argument);$formatted[$parts[0]]=$parts[1];}else{$formatted[$index]=$argument;}}return $formatted;}private static function parseCommandArguments():array{global $argc;global $argv;$options=[];$arguments=[];for($i=1;$i<$argc;$i++){if(str_starts_with($argv[$i],'-')){$options[]=$argv[$i];}else{$arguments[]=$argv[$i];}}return[self::parseOptions($options),self::parseArguments($arguments)];}}class Output{public static function write(string $string):void{file_put_contents('php://output',$string);}}class Input{public static function readline(?string $prompt=null):string{return readline($prompt);}public static function getline():string{return trim(fgets(Console::INPUT));}}class Command{use InteractsWithIO;use AccessesArguments;protected Output $output;protected array $options;protected array $arguments;protected function __construct(){$this->output=new Output();[$this->options,$this->arguments]=$this->parseCommandArguments();}public static function main(Closure $logic):int{$command=new static();$logic=$logic->bindTo($command,static::class);return $logic()?? 0;}}class Dumper{public static int $arrayBreakLevel=2;const INDENT=' ';const ARRAY_OPEN=ANSI::WHITE.'['.ANSI::RESET;const ARRAY_CLOSE=ANSI::WHITE.']'.ANSI::RESET;const STRING_OPEN=ANSI::BLUE."'".ANSI::GREEN;const STRING_CLOSE=ANSI::BLUE."'".ANSI::RESET;const INTEGER_OPEN=ANSI::YELLOW;const INTEGER_CLOSE=ANSI::RESET;const BOOLEAN_OPEN=ANSI::RED;const BOOLEAN_CLOSE=ANSI::RESET;const OBJECT_OPEN=ANSI::YELLOW;const OBJECT_CLOSE=ANSI::RESET;const NULL=ANSI::RED.'null'.ANSI::RESET;protected int $indentationLevel=0;protected bool $inOpenArray=false;public static function highlight(mixed $data):string{return(new static())->runHighlighter($data);}protected function runHighlighter(mixed $data):string{if(is_null($data)){return $this->null($data);}if(is_string($data)){return $this->string($data);}if(is_int($data)){return $this->int($data);}if(is_bool($data)){return $this->bool($data);}if(is_array($data)){return $this->array($data);}if(is_object($data)){return static::OBJECT_OPEN.$data::class.static::OBJECT_CLOSE;}return (string) $data;}protected function null(null|string $value):string{return static::NULL;}protected function string(string $value):string{return static::STRING_OPEN.$value.static::STRING_CLOSE;}protected function int(int $value):string{return static::INTEGER_OPEN.$value.static::INTEGER_CLOSE;}protected function bool(bool $value):string{return static::BOOLEAN_OPEN.($value?'true':'false').static::BOOLEAN_CLOSE;}protected function array(array $array):string{$this->indentationLevel++;if($this->indentationLevel>=static::$arrayBreakLevel-1){$this->inOpenArray=true;}$parts=[];foreach($array as $key=>$value){if($this->inOpenArray){$indent=str_repeat(self::INDENT,$this->indentationLevel);}else{$indent='';}if(is_int($key)){$parts[]=$indent.$this->runHighlighter($value);}else{$parts[]=$indent.$this->string($key).' => '.$this->runHighlighter($value);}}if($this->inOpenArray){$this->indentationLevel--;$indent=str_repeat(self::INDENT,$this->indentationLevel);return static::ARRAY_OPEN."\n".implode(",\n",$parts)."\n$indent".static::ARRAY_CLOSE;}else{return static::ARRAY_OPEN.''.implode(', ',$parts).''.static::ARRAY_CLOSE;}}}if(!function_exists('main')){function main(Closure $logic):int{return Command::main($logic);}}if(!function_exists('dump')){function dump(mixed $value,bool $highlight=false):void{if($highlight){echo Dumper::highlight($value)."\n";}else{var_dump($value);}}}if(!function_exists('dd')){function dd(mixed $value,bool $highlight=false):never{dump($value,$highlight);exit(1);}}if(!function_exists('task')){function task(string $name,callable $task):void{$timeStart=microtime(true);global $argv;if(in_array('--skip-tasks',$argv)){putenv('SKIP_TASKS=true');$setLocation='option';}Output::write(ANSI::GREEN.'Running task '.ANSI::YELLOW."$name".ANSI::GREEN.'...'.ANSI::RESET.' ');if(!getenv('SKIP_TASKS')){ob_start();$task();$buffer=ob_get_clean();$time=round((microtime(true)-$timeStart)*1000,2);Output::write(ANSI::GREEN.'Done! '.ANSI::GRAY."(took {$time}ms)"." \n".ANSI::RESET);}else{$setLocation=$setLocation ?? 'environment variable';Output::write(ANSI::YELLOW.'Skipped '.ANSI::GRAY."(as set in $setLocation)\n".ANSI::RESET);}if(!empty($buffer)){foreach(explode("\n",trim($buffer))as $line){Output::write(" $line\n");}}}}
110 changes: 110 additions & 0 deletions packages/hydefront/.github/scripts/post-build.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php

declare(strict_types=1);

require_once __DIR__ . '/minima.php';

exit(main(function (): int {
$baseDir = __DIR__.'/../../';

if ($this->hasOption('inject-version')) {
$package = json_decode(file_get_contents($baseDir.'package.json'), true);
$version = $package['version'];
$css = file_get_contents($baseDir.'dist/hyde.css');

if (str_contains($css, '/*! HydeFront')) {
$this->error('Version already injected in dist/hyde.css');
return 1;
}

$template = '/*! HydeFront v{{ $version }} | MIT License | https://hydephp.com*/';
$versionString = str_replace('{{ $version }}', $version, $template);
$css = "$versionString\n$css";
file_put_contents($baseDir.'dist/hyde.css', $css);

return 0;
}

$this->info('Verifying build files...');
$exitCode = 0;

$package = json_decode(file_get_contents($baseDir.'package.json'), true);
$version = $package['version'];
$this->line("Found version '$version' in package.json");

$hydeCssVersion = getCssVersion($baseDir.'dist/hyde.css');
$this->line("Found version '$hydeCssVersion' in dist/hyde.css");

$appCssVersion = getCssVersion($baseDir.'dist/app.css');
$this->line("Found version '$appCssVersion' in dist/app.css");

if ($this->hasOption('fix')) {
$this->info('Fixing build files...');

if ($version !== $hydeCssVersion) {
$this->line(' > Updating dist/hyde.css...');
$contents = file_get_contents($baseDir.'dist/hyde.css');
$contents = str_replace($hydeCssVersion, $version, $contents);
file_put_contents($baseDir.'dist/hyde.css', $contents);
$filesChanged = true;
}

if ($version !== $appCssVersion) {
$this->line(' > Updating dist/app.css...');
$contents = file_get_contents($baseDir.'dist/app.css');
$contents = str_replace($appCssVersion, $version, $contents);
file_put_contents($baseDir.'dist/app.css', $contents);
$filesChanged = true;
}

if (isset($filesChanged)) {
$this->info('Build files fixed');

// Run the script again to verify the changes, but without the --fix option
$this->info('Verifying build files again...');
$this->line('---');
passthru('php packages/hydefront/.github/scripts/post-build.php', $verifyExitCode);
return $verifyExitCode;
} else {
$this->warning('Nothing to fix!');
return 0;
}
}

if ($version !== $hydeCssVersion) {
$this->error('Version mismatch in package.json and dist/hyde.css:');
$this->warning("Expected hyde.css to have version '$version', but found '$hydeCssVersion'");
$exitCode = 1;
}

if ($version !== $appCssVersion) {
$this->error('Version mismatch in package.json and dist/app.css:');
$this->warning("Expected app.css to have version '$version', but found '$appCssVersion'");
$exitCode = 1;
}

if ($exitCode > 0) {
$this->error('Exiting with errors.');
} else {
$this->info('Build files verified. All looks good!');
}

return $exitCode;
}));

function getCssVersion(string $path): string
{
$contents = file_get_contents($path);
$prefix = '/*! HydeFront v';
if (! str_starts_with($contents, $prefix)) {
throw new Exception('Invalid CSS file');
}
$contents = substr($contents, strlen($prefix));
// Get everything before |
$pipePos = strpos($contents, '|');
if ($pipePos === false) {
throw new Exception('Invalid CSS file');
}
$contents = substr($contents, 0, $pipePos);
return trim($contents);
}
55 changes: 55 additions & 0 deletions packages/hydefront/.github/scripts/version.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

require_once __DIR__ . '/minima.php';

exit(main(function (): int {
if (! is_dir(getcwd() . '/packages')) {
$this->error('This script must be run from the root of the monorepo');
$this->warning('Current working directory: ' . getcwd());
return 1;
}

global $argv;
$versionType = $argv[1] ?? null;
if ($versionType === null) {
$this->error('Missing version type (supply as first argument)');
return 1;
}
/** @noinspection SpellCheckingInspection */
$nodeJsVersions = ['major', 'minor', 'patch', 'premajor', 'preminor', 'prepatch', 'prerelease'];
if (! in_array($versionType, $nodeJsVersions)) {
$this->error('Invalid version type: ' . $versionType);
$this->warning('Must be one of: '.implode(', ', $nodeJsVersions));
return 1;
}

$this->info("Creating a new HydeFront $versionType version...");
$version = trim(shell_exec('cd packages/hydefront && npm version ' . $versionType . ' --no-git-tag-version'));
$this->line("Updated package.json version to $version");

$this->info('Updating version in dist files...');
$this->line('---');
passthru('php packages/hydefront/.github/scripts/post-build.php --fix', $fixExitCode);
if ($fixExitCode !== 0) {
$this->error('Failed to update version in dist files');
return $fixExitCode;
}
$this->line('---');

$this->info('Committing changes in monorepo...');
passthru('git add packages/hydefront && git commit -m "Create HydeFront ' . $version . '"');

$this->info('Committing changes in HydeFront...');
passthru('cd packages/hydefront && git add . && git commit -m "HydeFront ' . $version . '"');

$this->info('All done!');
$this->warning("Don't forget to verify the changes, tag them, push them, then publish the NPM package and create the release on GitHub!");

if (! str_contains($versionType, 'patch')) {
$this->warning('This is not a patch version, so you should also update the version in the monorepo package.json');
}

return 0;
}));
20 changes: 20 additions & 0 deletions packages/hydefront/.github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Lint

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
lint:
name: Lint
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Run linter
run: php .github/scripts/post-build.php
4 changes: 0 additions & 4 deletions packages/hydefront/.github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ jobs:
- name: Build assets for production
run: npm run prod

- name: Add copyright text to compiled file
# add '/*! HydeFront v3.3.0 | MIT License | https://hydephp.com*/' to the top of the dist/hyde.css file
run: sed -i '1s/^/\/\*! HydeFront v3.3.0 | MIT License | https:\/\/hydephp.com\*\/\n/' dist/hyde.css

- name: Upload artifacts
uses: actions/upload-artifact@v1
with:
Expand Down
Loading