diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml
new file mode 100644
index 0000000..5cb98ab
--- /dev/null
+++ b/.github/workflows/php.yml
@@ -0,0 +1,53 @@
+name: PHP tests
+
+on:
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ build:
+
+ strategy:
+ matrix:
+ php: ['8.2', '8.3', '8.4']
+ symfony: ['6.4', '7.3']
+
+ runs-on: ubuntu-latest
+
+ name: On PHP ${{ matrix.php }} and Symfony ${{ matrix.symfony }}
+ steps:
+ # https://github.com/marketplace/actions/checkout
+ - name: Checkout
+ uses: actions/checkout@v5
+
+ # https://github.com/marketplace/actions/setup-php-action
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php-versions }}
+ extensions: mbstring, intl
+ ini-values: post_max_size=256M, max_execution_time=180
+ tools: composer
+
+ - name: Check PHP version
+ run: php -v
+
+ - name: Validate composer.json and composer.lock
+ run: composer validate
+
+ - name: Install dependencies
+ run: composer install --prefer-dist --no-progress
+
+ - name: Install Symfony ${{ matrix.symfony }} packages
+ run: |
+ composer update symfony/console:${{ matrix.symfony }}
+ composer update symfony/stopwatch:${{ matrix.symfony }}
+
+ - name: Code lint PHP files
+ run: ./vendor/bin/phplint
+
+ - name: Coding standards
+ run: ./vendor/bin/phpcs
+
+ - name: PHPUnit
+ run: ./vendor/bin/phpunit
diff --git a/.gitignore b/.gitignore
index 00d7507..3ddd3e8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
vendor
-.phpunit.result.cache
package-lock.json
-tests/example/apollo
composer.lock
-var/cache/*
\ No newline at end of file
+var/cache/*
+.phpunit.cache
+.phpunit.result.cache
+.phplint.cache
diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist
new file mode 100644
index 0000000..fdff1a3
--- /dev/null
+++ b/.phpcs.xml.dist
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+ src
+ tests
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ tests/*
+
+
+
\ No newline at end of file
diff --git a/.phplint.yml b/.phplint.yml
new file mode 100644
index 0000000..e1fc3c2
--- /dev/null
+++ b/.phplint.yml
@@ -0,0 +1,6 @@
+path: ./
+jobs: 10
+extensions:
+ - php
+exclude:
+ - vendor
\ No newline at end of file
diff --git a/README.md b/README.md
index fbda556..a01b55e 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@ Key features:
## Requirements
-* PHP 7.4+
+* PHP 8.1+
* [Composer](https://getcomposer.org/)
## Installation
diff --git a/composer.json b/composer.json
index 83cabdf..a53837d 100644
--- a/composer.json
+++ b/composer.json
@@ -4,17 +4,18 @@
"type": "library",
"license": "MIT",
"require": {
- "php": ">7.4.0",
+ "php": ">=8.2",
"twig/twig": "^3.0",
- "symfony/console": "^5.0",
- "symfony/stopwatch": "^5.0",
- "league/flysystem": "^2.2",
+ "symfony/console": "^6.4|^7.1",
+ "symfony/stopwatch": "^6.4|^7.1",
+ "league/flysystem": "^3.3",
"masterminds/html5": "^2.7",
- "league/commonmark": "^2.0",
- "alchemy/zippy": "^1.0"
+ "league/commonmark": "^2.0"
},
"require-dev": {
- "phpunit/phpunit": "^8",
+ "overtrue/phplint": "^9.0",
+ "phpunit/phpunit": ">=10.0",
+ "roave/security-advisories": "dev-latest",
"squizlabs/php_codesniffer": "^3.5"
},
"autoload": {
diff --git a/docs/contributing.md b/docs/contributing.md
index ab2f823..7ee9a86 100644
--- a/docs/contributing.md
+++ b/docs/contributing.md
@@ -50,18 +50,24 @@ configuration via the `src/Config.php` class.
You can test your changes by using the example project.
-Build files:
+Run Composer install in the root:
+```shell
+composer install
```
-cd tests/example
-../../bin/design-system
+
+If you have errors with this delete your local `composer.lock` file and try again.
+
+Build files:
+
+```shell
+bin/design-system --path=tests/example
```
Serve:
```
-cd _dist/
-php -S localhost:8000
+php -S localhost:8000 -t tests/example/_dist/
```
Test at: http://localhost:8000
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index b0486e9..0155190 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -1,26 +1,18 @@
-
-
-
-
-
-
-
-
-
- tests
-
-
-
-
-
- src
-
-
-
+
+
+
+
+
+
+
+ tests
+
+
+
+
+ src
+
+
diff --git a/src/Build.php b/src/Build.php
index d61a633..3eeeee1 100644
--- a/src/Build.php
+++ b/src/Build.php
@@ -1,9 +1,9 @@
output = $output;
// Set default file permissions
- $visibility = PortableVisibilityConverter::fromArray([
+ $visibility = PortableVisibilityConverter::fromArray(
+ [
'file' => [
'public' => 0644,
'private' => 0600,
@@ -52,8 +56,9 @@ public function __construct(Config $config, SymfonyStyle $output)
'public' => 0755,
'private' => 0700,
],
- ],
- Visibility::PUBLIC);
+ ],
+ Visibility::PUBLIC
+ );
$adapter = new LocalFilesystemAdapter($config->getRootPath(), $visibility);
$this->filesystem = new Filesystem($adapter);
$this->markdown = new Markdown();
@@ -110,7 +115,6 @@ public function cleanDestination(): void
try {
$this->filesystem->deleteDirectory($destination);
$this->filesystem->createDirectory($destination);
-
} catch (FilesystemException | UnableToDeleteDirectory $exception) {
throw new BuildException(sprintf('Cannot clean destination folder, error: %s', $exception->getMessage()));
}
@@ -130,13 +134,13 @@ public function buildAssets(bool $passthru = false)
}
// Change dir, then run command
- $command = sprintf('cd %s && %s',$this->config->getRootPath(), $command);
+ $command = sprintf('cd %s && %s', $this->config->getRootPath(), $command);
$output = '';
if ($passthru) {
- passthru($command,$status);
+ passthru($command, $status);
} else {
- exec($command,$output,$status);
+ exec($command, $output, $status);
}
if ($status !== 0) {
@@ -245,7 +249,7 @@ public function buildDocs()
// Sort layouts in each sub-directory
foreach ($pages as $subDirectory => $children) {
- uasort($pages[$subDirectory], function($a, $b) {
+ uasort($pages[$subDirectory], function ($a, $b) {
// Stick index layouts to top
if ($a['filename'] === 'index') {
return -1;
@@ -383,7 +387,7 @@ public function buildDocsPage(string $title, string $sourcePath, string $destina
/**
* Create ZIP file of website assets for developer use
*
- * @see https://github.com/alchemy-fr/Zippy
+ * @see https://www.php.net/manual/en/class.ziparchive.php
*/
public function buildZipFile()
{
@@ -391,11 +395,9 @@ public function buildZipFile()
$this->output->text('Skipping, no ZIP folder defined in config');
return false;
}
-
- // Path to folder to add to ZIP archive (relative to project root)
$zipFolder = $this->config->get('zip_folder');
if (empty($zipFolder)) {
- $this->output->text('Skipping, no ZIP folder defined in config');
+ $this->output->text('Skipping, no source folder to creat a ZIP defined in config');
return false;
}
$source = $this->config->getFullPath($zipFolder);
@@ -413,21 +415,29 @@ public function buildZipFile()
}
$destination = $this->config->getFullPath($this->config->buildPath(Config::ASSETS_PATH, $zipName)) . '.zip';
- try {
- $zippy = Zippy::load();
- $archive = $zippy->create($destination, [
- $zipName => $source
- ], true);
-
- if ($this->output->isVerbose()) {
- $this->output->text('* ' . $destination);
- }
+ // Setup ZIP archive
+ $zip = new ZipArchive();
+ if ($zip->open($destination, ZipArchive::CREATE) !== true) {
+ throw new BuildException(sprintf('Cannot create ZIP archive at %s', $destination));
+ }
- return true;
+ // ZIP folders
+ $info = pathinfo($source);
+ $zipFolderRegex = '/^' . preg_quote($info["dirname"] . '/' . $info["basename"], '/') . '/';
+
+ // Add all files in source folder to ZIP
+ $flags = \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_SELF;
+ $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source, $flags));
+ /** @var RecursiveDirectoryIterator $file */
+ foreach ($files as $file) {
+ $filepath = $file->getPathname();
+ $zipPath = preg_replace($zipFolderRegex, '', $filepath);
+ $zip->addFile($filepath, $zipPath);
+ }
- } catch (\Alchemy\Zippy\Exception\ExceptionInterface $exception) {
- throw new BuildException(sprintf('Cannot build ZIP archive for folder %s, destination %s, error: %s', $zipFolder, $destination, $exception->getMessage()));
+ if (!$zip->close()) {
+ throw new BuildException(sprintf('Cannot save ZIP archive at %s', $destination));
}
+ return true;
}
-
}
diff --git a/src/Command/BuildCommand.php b/src/Command/BuildCommand.php
index 0cb7864..449aa7e 100644
--- a/src/Command/BuildCommand.php
+++ b/src/Command/BuildCommand.php
@@ -1,4 +1,5 @@
setDescription('Build design system website')
@@ -43,16 +45,16 @@ protected function configure()
'actions',
'a',
InputOption::VALUE_REQUIRED,
- 'Which actions to run ("c" = clean, "a" = assets, "p" = layouts, "t" = templates)',
+ 'Which actions to run ("c" = clean, "a" = assets, "d" = docs, "z" = zip)',
'cadz'
)
;
}
- protected function execute(InputInterface $input, OutputInterface $output)
+ protected function execute(InputInterface $input, OutputInterface $output): int
{
$stopwatch = new Stopwatch();
- $stopwatch->start(self::$defaultName);
+ $stopwatch->start($this->getName());
$io = new SymfonyStyle($input, $output);
$io->title(Version::NAME . ': ' . $this->getDescription());
@@ -98,7 +100,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
}
// Finish up
- $event = $stopwatch->stop(self::$defaultName);
+ $event = $stopwatch->stop($this->getName());
$io->newLine();
$io->text(sprintf('Execution time: %01.2f secs', $event->getDuration() / 1000));
$io->text(sprintf('Memory usage: %01.2f MB', $event->getMemory() / 1024 / 1024));
@@ -125,5 +127,4 @@ private function doZip(): bool
{
return strpos($this->actions, 'z') !== false;
}
-
-}
\ No newline at end of file
+}
diff --git a/src/Command/InitCommand.php b/src/Command/InitCommand.php
index b600c96..8fd0964 100644
--- a/src/Command/InitCommand.php
+++ b/src/Command/InitCommand.php
@@ -1,4 +1,5 @@
setDescription('Initialise design system project')
@@ -34,7 +36,7 @@ protected function configure()
;
}
- protected function execute(InputInterface $input, OutputInterface $output)
+ protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$io->title(Version::NAME . ': ' . $this->getDescription());
@@ -66,4 +68,4 @@ protected function execute(InputInterface $input, OutputInterface $output)
// Finish up
return self::SUCCESS;
}
-}
\ No newline at end of file
+}
diff --git a/src/Command/WatchCommand.php b/src/Command/WatchCommand.php
deleted file mode 100644
index 94cc0eb..0000000
--- a/src/Command/WatchCommand.php
+++ /dev/null
@@ -1,30 +0,0 @@
-setDescription('Build & watch project files')
- ->setHelp('This command builds the project files and watches for any changes, on chance it rebuilds files')
- ;
- }
-
- protected function execute(InputInterface $input, OutputInterface $output)
- {
- $io = new SymfonyStyle($input, $output);
- $io->title('Apollo: Build & Watch your project files');
-
- return 0;
- }
-}
\ No newline at end of file
diff --git a/src/Config.php b/src/Config.php
index cb0dc96..2a5e1b7 100644
--- a/src/Config.php
+++ b/src/Config.php
@@ -48,7 +48,7 @@ class Config
* @throws PathDoesNotExistException
* @throws \League\Flysystem\FilesystemException
*/
- public function __construct(string $rootPath, string $configPath = null)
+ public function __construct(string $rootPath, ?string $configPath = null)
{
$this->setRootPath($rootPath);
$adapter = new LocalFilesystemAdapter($rootPath);
@@ -65,7 +65,7 @@ public function __construct(string $rootPath, string $configPath = null)
* @param ?string $currentUrl
* @return array
*/
- public function getNavigation(string $currentUrl = null): array
+ public function getNavigation(?string $currentUrl = null): array
{
$navigation = [];
foreach ($this->get('navigation') as $label => $url) {
@@ -130,7 +130,7 @@ public function loadConfig(string $configPath)
}
// Require config file, which must contain a $config array
- require $configPath;
+ require $this->getFullPath($configPath);
if (!isset($config) || !is_array($config)) {
throw new ConfigException(sprintf('Config file %s must contain the $config variable and it must be an array', $configPath));
}
@@ -263,5 +263,4 @@ public function getPageTitle(string $title): string
}
return $title;
}
-
-}
\ No newline at end of file
+}
diff --git a/src/Exception/AssetsException.php b/src/Exception/AssetsException.php
index ef0e8e0..5fe7f39 100644
--- a/src/Exception/AssetsException.php
+++ b/src/Exception/AssetsException.php
@@ -1,9 +1,9 @@
currentHtmlMatch, $this->currentFile));
}
}
-
-}
\ No newline at end of file
+}
diff --git a/src/Parser/ExampleParser.php b/src/Parser/ExampleParser.php
index 959f828..08d1cce 100644
--- a/src/Parser/ExampleParser.php
+++ b/src/Parser/ExampleParser.php
@@ -1,4 +1,5 @@
output->isVerbose()) {
$this->output->text('* ' . $destination);
}
-
} catch (FilesystemException | UnableToWriteFile $exception) {
throw new ExampleTagException(sprintf('Cannot save example template to %s (%s). Error with tag %s in doc file %s', $filename, $exception->getMessage(), $this->currentHtmlMatch, $this->currentFile));
}
@@ -126,5 +126,4 @@ public function render(array $params): string
];
return $this->twig->render('@DesignSystem/partials/_example.html.twig', $data);
}
-
-}
\ No newline at end of file
+}
diff --git a/src/Parser/Markdown.php b/src/Parser/Markdown.php
index a996229..c3448ee 100644
--- a/src/Parser/Markdown.php
+++ b/src/Parser/Markdown.php
@@ -62,7 +62,6 @@ public function linkProcessor(DocumentParsedEvent $event)
/** @var Link $node */
foreach ($matchingNodes as $node) {
-
// Only update if a local URL
$info = parse_url($node->getUrl());
if (count($info) > 1 && isset($info['host'])) {
@@ -97,5 +96,4 @@ public function render(string $content): string
{
return $this->getConvertor()->convert($content);
}
-
-}
\ No newline at end of file
+}
diff --git a/src/Parser/ParserAbstract.php b/src/Parser/ParserAbstract.php
index a4310ea..e4f14a7 100644
--- a/src/Parser/ParserAbstract.php
+++ b/src/Parser/ParserAbstract.php
@@ -1,4 +1,5 @@
assertTrue($resultCode === 0, $result);
unlink($testConfigPath);
}
-
-}
\ No newline at end of file
+}
diff --git a/tests/MarkdownTest.php b/tests/MarkdownTest.php
index 0b77c69..be5040d 100644
--- a/tests/MarkdownTest.php
+++ b/tests/MarkdownTest.php
@@ -7,7 +7,6 @@
class MarkdownTest extends TestCase
{
-
public function testMarkdown()
{
$markdown = new Markdown();
@@ -36,5 +35,4 @@ public function testMarkdown()
$this->assertStringNotContainsString('¶Hello testing
', $html, 'Auto-link headings');
$this->assertStringContainsString('¶Sub-heading
', $html, 'Auto-link headings');
}
-
}
diff --git a/tests/ParseSpecialFunctionsTest.php b/tests/ParseSpecialFunctionsTest.php
index 2abd650..dd6919d 100644
--- a/tests/ParseSpecialFunctionsTest.php
+++ b/tests/ParseSpecialFunctionsTest.php
@@ -8,7 +8,6 @@
final class ParseSpecialFunctionsTest extends TestCase
{
-
public function testMatchAll()
{
$parser = new TagParser();
@@ -46,6 +45,4 @@ public function testInvalidHtmlTag()
$this->expectException(HtmlParserException::class);
$matches = $parser->matchAll('some text
', 'example');
}
-
-
-}
\ No newline at end of file
+}
diff --git a/tests/example/design-system-config.php b/tests/example/design-system-config.php
index 98e89c6..ad310fa 100644
--- a/tests/example/design-system-config.php
+++ b/tests/example/design-system-config.php
@@ -6,6 +6,7 @@
* Overrides default config settings
* @see Studio24\DesignSystem\Config::$config
*/
+
$config = [
'site_title' => 'Studio 24 Design System',
'navigation' => [
@@ -14,6 +15,5 @@
'Guidelines' => '/guidelines/',
'Templates' => '/templates/',
],
- 'zip_folder' => 'apollo/assets'
+ 'zip_folder' => '_dist/assets/design-system'
];
-