Skip to content

Commit 2aed568

Browse files
authored
Merge pull request #1360 from hydephp/add-internal-hydefront-post-build-script
Add internal HydeFronts automation scripts to lint and handle releases and versions
2 parents 9222130 + 2f800b4 commit 2aed568

File tree

11 files changed

+209
-15
lines changed

11 files changed

+209
-15
lines changed

.github/workflows/continuous-integration.yml

+13-4
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,6 @@ jobs:
106106
working-directory: 'packages/hydefront'
107107
run: npm run prod
108108

109-
- name: Add copyright text to compiled file
110-
# add '/*! HydeFront v3.3.0 | MIT License | https://hydephp.com*/' to the top of the packages/hydefront/dist/hyde.css file
111-
run: sed -i '1s/^/\/\*! HydeFront v3.3.0 | MIT License | https:\/\/hydephp.com\*\/\n/' packages/hydefront/dist/hyde.css
112-
113109
- name: Upload artifacts
114110
uses: actions/upload-artifact@v1
115111
with:
@@ -588,3 +584,16 @@ jobs:
588584
uses: github/codeql-action/upload-sarif@v2
589585
with:
590586
sarif_file: snyk.sarif
587+
588+
589+
hydefront-lint:
590+
name: HydeFront Lint
591+
runs-on: ubuntu-latest
592+
permissions:
593+
contents: read
594+
steps:
595+
- name: Checkout code
596+
uses: actions/checkout@v3
597+
598+
- name: Run linter
599+
run: php packages/hydefront/.github/scripts/post-build.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?php
2+
3+
// This file is part of Minima.
4+
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");}}}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
require_once __DIR__ . '/minima.php';
6+
7+
exit(main(function (): int {
8+
$baseDir = __DIR__.'/../../';
9+
10+
if ($this->hasOption('inject-version')) {
11+
$package = json_decode(file_get_contents($baseDir.'package.json'), true);
12+
$version = $package['version'];
13+
$css = file_get_contents($baseDir.'dist/hyde.css');
14+
15+
if (str_contains($css, '/*! HydeFront')) {
16+
$this->error('Version already injected in dist/hyde.css');
17+
return 1;
18+
}
19+
20+
$template = '/*! HydeFront v{{ $version }} | MIT License | https://hydephp.com*/';
21+
$versionString = str_replace('{{ $version }}', $version, $template);
22+
$css = "$versionString\n$css";
23+
file_put_contents($baseDir.'dist/hyde.css', $css);
24+
25+
return 0;
26+
}
27+
28+
$this->info('Verifying build files...');
29+
$exitCode = 0;
30+
31+
$package = json_decode(file_get_contents($baseDir.'package.json'), true);
32+
$version = $package['version'];
33+
$this->line("Found version '$version' in package.json");
34+
35+
$hydeCssVersion = getCssVersion($baseDir.'dist/hyde.css');
36+
$this->line("Found version '$hydeCssVersion' in dist/hyde.css");
37+
38+
$appCssVersion = getCssVersion($baseDir.'dist/app.css');
39+
$this->line("Found version '$appCssVersion' in dist/app.css");
40+
41+
if ($this->hasOption('fix')) {
42+
$this->info('Fixing build files...');
43+
44+
if ($version !== $hydeCssVersion) {
45+
$this->line(' > Updating dist/hyde.css...');
46+
$contents = file_get_contents($baseDir.'dist/hyde.css');
47+
$contents = str_replace($hydeCssVersion, $version, $contents);
48+
file_put_contents($baseDir.'dist/hyde.css', $contents);
49+
$filesChanged = true;
50+
}
51+
52+
if ($version !== $appCssVersion) {
53+
$this->line(' > Updating dist/app.css...');
54+
$contents = file_get_contents($baseDir.'dist/app.css');
55+
$contents = str_replace($appCssVersion, $version, $contents);
56+
file_put_contents($baseDir.'dist/app.css', $contents);
57+
$filesChanged = true;
58+
}
59+
60+
if (isset($filesChanged)) {
61+
$this->info('Build files fixed');
62+
63+
// Run the script again to verify the changes, but without the --fix option
64+
$this->info('Verifying build files again...');
65+
$this->line('---');
66+
passthru('php packages/hydefront/.github/scripts/post-build.php', $verifyExitCode);
67+
return $verifyExitCode;
68+
} else {
69+
$this->warning('Nothing to fix!');
70+
return 0;
71+
}
72+
}
73+
74+
if ($version !== $hydeCssVersion) {
75+
$this->error('Version mismatch in package.json and dist/hyde.css:');
76+
$this->warning("Expected hyde.css to have version '$version', but found '$hydeCssVersion'");
77+
$exitCode = 1;
78+
}
79+
80+
if ($version !== $appCssVersion) {
81+
$this->error('Version mismatch in package.json and dist/app.css:');
82+
$this->warning("Expected app.css to have version '$version', but found '$appCssVersion'");
83+
$exitCode = 1;
84+
}
85+
86+
if ($exitCode > 0) {
87+
$this->error('Exiting with errors.');
88+
} else {
89+
$this->info('Build files verified. All looks good!');
90+
}
91+
92+
return $exitCode;
93+
}));
94+
95+
function getCssVersion(string $path): string
96+
{
97+
$contents = file_get_contents($path);
98+
$prefix = '/*! HydeFront v';
99+
if (! str_starts_with($contents, $prefix)) {
100+
throw new Exception('Invalid CSS file');
101+
}
102+
$contents = substr($contents, strlen($prefix));
103+
// Get everything before |
104+
$pipePos = strpos($contents, '|');
105+
if ($pipePos === false) {
106+
throw new Exception('Invalid CSS file');
107+
}
108+
$contents = substr($contents, 0, $pipePos);
109+
return trim($contents);
110+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
require_once __DIR__ . '/minima.php';
6+
7+
exit(main(function (): int {
8+
if (! is_dir(getcwd() . '/packages')) {
9+
$this->error('This script must be run from the root of the monorepo');
10+
$this->warning('Current working directory: ' . getcwd());
11+
return 1;
12+
}
13+
14+
global $argv;
15+
$versionType = $argv[1] ?? null;
16+
if ($versionType === null) {
17+
$this->error('Missing version type (supply as first argument)');
18+
return 1;
19+
}
20+
/** @noinspection SpellCheckingInspection */
21+
$nodeJsVersions = ['major', 'minor', 'patch', 'premajor', 'preminor', 'prepatch', 'prerelease'];
22+
if (! in_array($versionType, $nodeJsVersions)) {
23+
$this->error('Invalid version type: ' . $versionType);
24+
$this->warning('Must be one of: '.implode(', ', $nodeJsVersions));
25+
return 1;
26+
}
27+
28+
$this->info("Creating a new HydeFront $versionType version...");
29+
$version = trim(shell_exec('cd packages/hydefront && npm version ' . $versionType . ' --no-git-tag-version'));
30+
$this->line("Updated package.json version to $version");
31+
32+
$this->info('Updating version in dist files...');
33+
$this->line('---');
34+
passthru('php packages/hydefront/.github/scripts/post-build.php --fix', $fixExitCode);
35+
if ($fixExitCode !== 0) {
36+
$this->error('Failed to update version in dist files');
37+
return $fixExitCode;
38+
}
39+
$this->line('---');
40+
41+
$this->info('Committing changes in monorepo...');
42+
passthru('git add packages/hydefront && git commit -m "Create HydeFront ' . $version . '"');
43+
44+
$this->info('Committing changes in HydeFront...');
45+
passthru('cd packages/hydefront && git add . && git commit -m "HydeFront ' . $version . '"');
46+
47+
$this->info('All done!');
48+
$this->warning("Don't forget to verify the changes, tag them, push them, then publish the NPM package and create the release on GitHub!");
49+
50+
if (! str_contains($versionType, 'patch')) {
51+
$this->warning('This is not a patch version, so you should also update the version in the monorepo package.json');
52+
}
53+
54+
return 0;
55+
}));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: Lint
2+
3+
on:
4+
push:
5+
branches: [ master ]
6+
pull_request:
7+
branches: [ master ]
8+
9+
jobs:
10+
lint:
11+
name: Lint
12+
runs-on: ubuntu-latest
13+
permissions:
14+
contents: read
15+
steps:
16+
- name: Checkout code
17+
uses: actions/checkout@v3
18+
19+
- name: Run linter
20+
run: php .github/scripts/post-build.php

packages/hydefront/.github/workflows/node.js.yml

-4
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,6 @@ jobs:
2626
- name: Build assets for production
2727
run: npm run prod
2828

29-
- name: Add copyright text to compiled file
30-
# add '/*! HydeFront v3.3.0 | MIT License | https://hydephp.com*/' to the top of the dist/hyde.css file
31-
run: sed -i '1s/^/\/\*! HydeFront v3.3.0 | MIT License | https:\/\/hydephp.com\*\/\n/' dist/hyde.css
32-
3329
- name: Upload artifacts
3430
uses: actions/upload-artifact@v1
3531
with:

0 commit comments

Comments
 (0)