What does the "key: value" syntax do in composer scripts - php

From compser documentation you can write scripts like so:
{
"scripts": {
"some-event": [
"command1",
"command2",
"command3"
],
}
}
But in Symfony I found a slightly different syntax that instead of a list is using a key:value pair like this:
{
"scripts": {
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd"
},
},
}
How does this syntax work exactly?
Does it redirect commands to another one, adds a dependency between 2 commands or what?

How does this syntax work exactly?
This syntax is undefined in Composer.
To learn more, remove it and then check which component(s) break(s). Then consult the support of that/these component(s) to learn more (and/or leave a comment here).
Does it redirect commands to another one, adds a dependency between 2 commands or what?
As written this is undefined. As far as you're concerned about Composer you can check if it is recognized as a script:
composer run-script --list
You'll likely see that auto-scripts is listed as a script, that is, Composer is recognizing at least the auto-scripts keyword.
You can then execute that script to see how Composer executes it (it is highly recommended you do this and the following in an isolated environment):
composer run-script auto-scripts
To see if a plugin would make a difference, you can also execute it with plugins disabled:
composer --no-plugins run-script auto-scripts
Take care.
And with Composer there is always -vvv.
Some notes on internal Composer configuration file handling: JSON Text files (.json, .lock) in Composer are typically parsed into associative arrays (not stdClass::class objects).
Therefore foreach'ing a script member - which can be a string as well, likely post-processed with an (array) cast - would not care about the key (attribute name), just executing the scripts lines.

This syntax is used by symfony/flex plugin and is only valid under auto-scripts key.
The code in flex looks like this:
$json = new JsonFile(Factory::getComposerFile());
$jsonContents = $json->read();
$executor = new ScriptExecutor($this->composer, $this->io, $this->options);
foreach ($jsonContents['scripts']['auto-scripts'] as $cmd => $type) {
$executor->execute($type, $cmd);
}
With execute being handled using this code:
switch ($type) {
case 'symfony-cmd':
return $this->expandSymfonyCmd($cmd);
case 'php-script':
return $this->expandPhpScript($cmd);
case 'script':
return $cmd;
default:
throw new \InvalidArgumentException();
}

Related

Instruct composer to use different class when ambiguous

When performing a composer update, I received the following warning:
Warning: Ambiguous class resolution, "Normalizer" was found in both "/var/www/concrete5/vendor/patchwork/utf8/src/Normalizer.php" and "/var/www/concrete5/vendor/voku/portable-utf8/src/Normalizer.php", the first will be used.
The site is now experiencing errors which I think might be related.
How can I instruct composer to use the second file (i.e. /var/www/concrete5/vendor/voku/portable-utf8/src/Normalizer.php) instead of the first?
Note that I have tried adding the following to exclude-from-classmap to composer.json and while it suppresses the warning, doesn't appear to have any impact.
"autoload-dev": {
"psr-4": {
"ConcreteComposer\\" : "./tests"
},
"exclude-from-classmap": [
"vendor/patchwork/utf8/src/Normalizer.php"
]
},
exclude-from-classmap has effect only when classmap is used for autoloading specified class. In your case class is loaded using PSR rules, but you may use optimized autolader, which generates classmap for all classes:
composer install -o
Also, since you placed this rule inside of autoload-dev, it will not have effect when you run composer install with --no-dev flag.

How write a Symfony Flex recipe for a new bundle?

I tried to find any documentation about using Symfony Flex but so far no luck.
Almost all docs point to installing a bundle that uses symfony Flex, not how to create a bundle that is using it.
I even tried to reverse engineer some of the packages but again, no luck.
My goal is to generate a default configuration file for my bundle in config/packages/my_bundle.yaml.
What I need to know is where do I need to put it and what env variables (if any) will I have available?
What is a Flex Recipe?
Keep in mind that the flex recipe is a separate repository from your package repository, that needs to be hosted separately from the Bundle package.
In the most likely scenario that your is a public bundle/recipe, you'll have to submit your recipe to the "contrib" repository, get it approved and merged, so it's available as a community recipe.
Additionally, it's important to remember that most users will not have the contrib repository enabled by default. So if this is important for installing this bundle, you should tell your users how to do so before they install your recipe (e.g. in your bundle's readme file).
Private Recipes
The other option would be having a private Flex recipe, as described here. The easiest way to generate a private recipe is to follow the same steps that Symfony does. Check this question and its answers for more details: How to generate a private recipe JSON from the contents of a recipe directory?
With that out of the way: Basically, a Flex recipe is a repository with a manifest.json file with specific keys to enable certain "configurators".
The available manifest.json configurators are:
Bundles
Which bundles should be enabled on bundles.php. These are added when the recipe is installed, and removed when the recipe is uninstalled.
{
"bundles": {
"Symfony\\Bundle\\DebugBundle\\DebugBundle": ["dev", "test"],
"Symfony\\Bundle\\MonologBundle\\MonologBundle": ["all"]
}
}
Configuration
The "configuration" configurator deals with two keys: copy-from-recipe and copy-from-package. The first one can copy files from the recipe repository, the second one copies files from the package repository.
{
"copy-from-package": {
"bin/check.php": "%BIN_DIR%/check.php"
},
"copy-from-recipe": {
"config/": "%CONFIG_DIR%/",
"src/": "%SRC_DIR%/"
}
}
In this example, a file bin/check.php in the package will be copied to the projects %BIN_DIR%, and the contents of config and src on the recipe package will be copied the corresponding directory.
This is the typical use case to provide default configuration files, for example. From what you ask, this is your stated purpose for wanting to create a flex recipe.
Env Vars
This configurator simply adds the appropriate environment variable values to the project's .env and .env.dist. (Again, these would be removed if you uninstalled the recipe)
{
"env": {
"APP_ENV": "dev",
"APP_DEBUG": "1"
}
}
Composer Scripts
This configurator adds tasks to the scripts:auto-scripts array from the project's composer.json. The auto-scripts are tasks that are executed every time composer update or composer install are executed in the project.
{
"composer-scripts": {
"vendor/bin/security-checker security:check": "php-script",
"make cache-warmup": "script",
"assets:install --symlink --relative %PUBLIC_DIR%": "symfony-cmd"
}
}
The second part on each line specifies what kind of command it is: a regular PHP script (php-script), a shell script (script), or a Symfony command (symfony-cmd, executed via bin/console).
Gitignore
This will add entries to the project's .gitignore file.
{
"gitignore": [
"/phpunit.xml"
]
}
A complete example of a manifest.json (lifted from here, as most other examples on this post):
{
"bundles": {
"Symfony\\Bundle\\FrameworkBundle\\FrameworkBundle": ["all"]
},
"copy-from-recipe": {
"config/": "%CONFIG_DIR%/",
"public/": "%PUBLIC_DIR%/",
"src/": "%SRC_DIR%/"
},
"composer-scripts": {
"make cache-warmup": "script",
"assets:install --symlink --relative %PUBLIC_DIR%": "symfony-cmd"
},
"env": {
"APP_ENV": "dev",
"APP_DEBUG": "1",
"APP_SECRET": "%generate(secret)%"
},
"gitignore": [
".env",
"/public/bundles/",
"/var/",
"/vendor/"
]
}
Additional configurators
There are two configurators which do not rely on the manifest.json file:
Post-install output.
If a file named post-install.txt exists in the recipe's package, its content is displayed when installation is complete. You can even use styles as defined here, for additional prettiness/obnoxiousness.
Example:
<bg=green;fg=white> </>
<bg=green;fg=white> Much success!! </>
<bg=green;fg=white> </>
* <fg=yellow>Next steps:</>
1. Foo
2. <comment>bar</>;
3. Baz <comment>https://example.com/</>.
This will be presented to the user after the installation is complete.
Makefile
If a file named Makefile exists in the recipe's repository, the tasks defined here would be added to the project's Makefile (creating the Makefile if it didn't exist).
cache-clear:
#test -f bin/console && bin/console cache:clear --no-warmup || rm -rf var/cache/*
.PHONY: cache-clear
Simple as that. I guess than most packages would not need a makefile command, so this would have much less use than other configurators.
You can read the full documentation here.

How to fix github oauth token for invalid characters: "__TOKEN__"? [duplicate]

I am trying to install a github project using composer and get the following error
Composer [UnexpectedValueException]
Your Github oauth token for github.com contains invalid characters: ""
Can anyone explain what I need to do to correct this error?
I am using the following command
composer create-project --prefer-dist --stability=dev vova07/yii2-start yii2-start
Thank you
I started getting a similar error and the reason was that Github recently changed the format of their auth tokens:
https://github.blog/changelog/2021-03-31-authentication-token-format-updates-are-generally-available/
To resolve the error:
Find the composer/auth.json file (if you're running the project in a container, you'll have to bash into it and find the file in there)
Remove its github.com entry. Your file will probably look like the following after removing the entry: {"github-oauth": {}}
Run composer self-update. The issue got resolved in version 2.0.12. See the first item in the changelog for that version here: https://getcomposer.org/changelog/2.0.12
After that, you can restore your composer/auth.json file to its initial state as the newer version of composer will recognize the new key format.
You can try Basic Auth instead:
Change this (oauth):
"github-oauth": {
"github.com": "ghp_[YOUR-PERSONAL-TOKEN]"
}
To this (basic auth):
"http-basic": {
"github.com": {
"username": "[YOUR-GITHUB-USERNAME]",
"password": "ghp_[YOUR-PERSONAL-TOKEN]"
}
}
You can find instructions on how to create a Personal Access Token
Inspired from github docs. Apparently, you can use Basic Authentication with a Personal Access token instead of oauth in some cases (e.g. like mine: installing a private git repo with composer).
I fixed it.
Goto C:\Users\XXXXX\AppData\Roaming\Composer
Open the auth.json
delete the github.com entry under "github-oauth": {}
That's it.
Update answer for Masiorama and Ruchir Mehta:
If you looking for file auth.json but don't know how, use this command:
locate auth.json
And here's the result:
You can see that auth.json will look like this:
/home/{your user name}/.config/composer/auth.json
Then you could use this command to edit the file:
sudo gedit /home/dev/.config/composer/auth.json
And remove content inside github-oauth.
If you're on MacOS, the auth.json file is at ~/.composer/auth.json. Then from there, you can remove the value for github-oauth. I tried fully deleting the file but I got a parse error, Expected one of: 'STRING', 'NUMBER', 'NULL', 'TRUE', 'FALSE', '{', '['. Your auth.json file should look like this:
{
"github-oauth": {}
}
This is similar to other answers posted but I wasn't able to use the locate command on MacOS so this might be helpful to other Mac users
This error recently popped up from nowhere.
Simply deleting the whole auth file worked for me..! Not sure why / when it appeared in the first place.
~/.composer/auth.json
As far as I know (I'm a beginner with composer too), the problem is with your authentication, so you have to fix your credentials in auth.json inside path-to-composer/.composer/
Inside you will find a json which will probably looks like:
{
"github-oauth": {
"github.com": null
}
}
Fix that and you should be ok ;)
The solution is just to upgrade your Composer version
using command composer self-update.
Go to C:\Users\UserName\AppData\Roaming\Composer
Open the auth.json file.
Clear everything and paste the below code
{
"bitbucket-oauth": {},
"github-oauth": {},
"gitlab-oauth": {},
"gitlab-token": {},
"http-basic": {},
"bearer": {}
}
I hope it will be solved
I run in the same problem after upgrading githup api token to the new format.
The answer is you need to upgrade composer version 1.10.21 or higher that fixes this problem.
Same solution as the answer of Paulina Khew but with command lines on MacOS :
cd ~/.composer/
nano auth.json
Delete what is inside th bracket :
{
"github-oauth": {}
}
When you're ready to save the file, hold down the Ctrl key and press the letter O
Press the Enter key on your keyboard to save.
When finished, press Ctrl + X to close nano and return to your shell.
Edit the composer authentication configuration file ~/.composer/auth.json
Then replace the following.
"http-basic": {
"github.com": {
"username": "[YOUR-GITHUB-USERNAME]",
"password": "ghp_[YOUR-PERSONAL-TOKEN]"
}
}
Now run the command composer install
That's a bug.
If you have Debian or Ubuntu, try this patch. Otherwise read the last line.
Quick copy-paste patch
If you have Debian 10 buster or Ubuntu 20.LTS or similar distributions, try this copy-paste command:
wget https://gist.githubusercontent.com/valerio-bozzolan/84364c28a3bba13751c504214016adcf/raw/c1356d529c89c10de4c959058e2e86ffe58fa407/fix-composer.patch -O /tmp/fix-composer.patch
sudo patch /usr/share/php/Composer/IO/BaseIO.php /tmp/fix-composer.patch
If it does not work, write it in the comments.
Step-by-step explaination
Your Composer version has a bug: you are able to save a valid GitHub token, but then it's not able to read that token again because Composer thinks that your GitHub token cannot contain underscores or stuff like that. Moreover, it's strange that Composer checks its syntax only the second time. Why? that's another story.
The fix is simple. You can temporary disable that wrong validation in your Composer version. Also because GitHub is a proprietary service and their specifications can change over time (as you demonstrated today). So it makes sense not to validate the syntax of GitHub tokens. The only person who should hard-validate GitHub tokens is GitHub itself, not Composer.
If you installed Composer via apt install composer, probably you will not have any update available and surely you cannot use self-update because Composer is read-only for security reasons (and for a similar reason, you should not execute Composer from root). Instead, you can create a safe hot-patch to fix that specific issue.
To create a patch, create a file called /tmp/fix-composer.patch with this exact content:
103,105c103,105
< if (!preg_match('{^[.a-z0-9]+$}', $token)) {
< throw new \UnexpectedValueException('Your github oauth token for '.$domain.' contains invalid characters: "'.$token.'"');
< }
---
> // if (!preg_match('{^[.a-z0-9]+$}', $token)) {
> // throw new \UnexpectedValueException('Your github oauth token for '.$domain.' contains invalid characters: "'.$token.'"');
> //
That content can also be seen from here:
https://gist.github.com/valerio-bozzolan/84364c28a3bba13751c504214016adcf
Then run this command to apply that patch:
sudo patch /usr/share/php/Composer/IO/BaseIO.php /tmp/fix-composer.patch
If it does not work, probably you have not installed composer via apt.
In short, whatever operating system, and whatever installation method, locate the file BaseIO.php in your Composer and comment out the validation check.

How to force Composer script to load local class instead of global class

undefined method
(Relevant files linked at the bottom of my question.)
I let Composer run some post-install-cmd and post-update-cmd scripts. In my script I want to make use of the readlink() function from symfony/filesystem. Inside my projects /vendor folder there is the 3.4 version of the filesystem package, fine.
I use Symfony\Component\Filesystem\Filesystem; at the top of my file.
But whenever I run:
$fs = new Filesystem();
$path = '/path/to/some/symlink';
if ($fs->readlink($path)) {
// code
}
I get the following error which tells me I'm calling an undefined method:
PHP Fatal error: Uncaught Error: Call to undefined method
Symfony\Component\Filesystem\Filesystem::readlink() in
/Users/leymannx/Sites/blog/scripts/composer/ScriptHandler.php:160
OK, so I double-checked the class inside my project's /vendor folder. This method is there. My IDE points me there. But when I run:
$fs = new Filesystem();
get_class_methods($fs);
this method is not listed.
Which file is it trying to load the method from?
OK, so I tried to check which file it's loading this class from:
$fs = new Filesystem();
$a = new \ReflectionClass($fs);
echo $a->getFileName();
and that returns me phar:///usr/local/Cellar/composer/1.7.2/bin/composer/vendor/symfony/filesystem/Filesystem.php – But why? Why is it taking the package from my Mac's Cellar? That's odd.
But OK, so I thought that's a Homebrew issue, and uninstalled the Homebrew Composer $ brew uninstall --force composer and installed it again as PHAR like documented on https://getcomposer.org/doc/00-intro.md#globally.
But now it's the same.
$fs = new Filesystem();
$a = new \ReflectionClass($fs);
echo $a->getFileName();
returns me phar:///usr/local/bin/composer/vendor/symfony/filesystem/Filesystem.php.
But why? Why does it pick up the (outdated) package from my global Composer installation? How can I force my script to use the project's local class and not the one from my global Composer installation?
What else?
Initially my $PATH contained /Users/leymannx/.composer/vendor/bin /usr/local/bin /usr/bin /bin /usr/sbin /sbin. I removed /Users/leymannx/.composer/vendor/bin to only return /usr/local/bin /usr/bin /bin /usr/sbin /sbin. Still the same.
I also tried setting the following in my composer.json. Still the same:
"optimize-autoloader": true,
"classmap-authoritative": true,
"vendor-dir": "vendor/",
I finally created an issue on GitHub: https://github.com/composer/composer/issues/7708
https://github.com/leymannx/wordpress-project/blob/master/composer.json
https://github.com/leymannx/wordpress-project/blob/master/scripts/composer/ScriptHandler.php
This is matter of context where your code is run. If you're executing some method directly in post-install-cmd it will be executed inside of Composer's process. It means that it will share all code bundled inside of composer.phar. Since you can't have two classes with the same FQN, you can't include another Symfony\Component\Filesystem\Filesystem in this context.
You can bypass this by running your script inside of separate process. You may create post-install-cmd.php file where you do all bootstrapping (like require vendor/autoload.php) and call these methods. Then run this file in your post-install-cmd hook:
"scripts": {
"post-install-cmd": [
"php post-install-cmd.php"
]
},

composer - dynamically set parameters variable

I have the following setup:
symfony 2.7 classic structure
composer for dependency management
What I need to do is set a variable in parameters.yml with the timestamp when composer was ran.
For this I tried the following solution:
parameters.yml.dist
[bla bla bla]
ran_timestamp: ~
composer.json
[bla bla bla]
"scripts": {
"pre-install-cmd": [
"export SYMFONY_APP_DATE=$(date +\"%s\")"
],
}
"extra": {
"incenteev-parameters": {
"file": "app/config/parameters.yml",
"env-map": {
"ran_timestamp": "SYMFONY_APP_DATE"
}
}
}
The part where the variable is set inside parameters.yml works fine (the parameter is created with the value from SYMFONY_APP_DATE env variable).
The problem is that the env variable is not updated when composer is ran. Can anyone help me with that pls?
Additional info:
If I run the command from pre-install-cmd in cli by hand it works fine (so command itself I think is ok)
I see the command being run in composer after it starts install so I think it is executed (output below):
$composer install
export SYMFONY_APP_DATE=$(date +"%s")
Loading composer repositories with package information [bla bla bla]
No errors are reported
I'm assuming maybe composer doesn't have rights to set env variables? - nope, it isn't this. It is related with variable scope.
The problem apparently is that you're setting env parameter in child process (which is created for each script), but it's not possible to redefine env parameter for parent process from child (i.e. to set env value for composer itself from one of its scripts)
I think you need to extend \Incenteev\ParameterHandler\ScriptHandler::buildParameters to make it happen.
UPD: I've found a way to make it happen
Define a special block only for build-params in composer.json
"scripts": {
"build-params": [
"Incenteev\\ParameterHandler\\ScriptHandler::buildParameters"
],
and than in post-install-cmd block instead of Incenteev\\ParameterHandler\\ScriptHandler::buildParameters make it
"export SYMFONY_APP_DATE=$(date +\"%s\") && composer run-script build-params"
That will create env var and building parameteres in same process

Categories