Wysyłanie argumentów wiersza poleceń do skryptu npm


817

scriptsCzęść mojego package.jsonaktualnie wygląda tak:

"scripts": {
    "start": "node ./script.js server"
}

... co oznacza, że ​​mogę uruchomić, npm startaby uruchomić serwer. Na razie w porządku.

Chciałbym jednak móc uruchomić coś podobnego npm start 8080i przekazać argument (y) do script.js(np. npm start 8080=> node ./script.js server 8080). czy to możliwe?

Odpowiedzi:


1129

Edytuj 2014.10.30: Możliwe jest przekazywanie argumentów do npm runnpm 2.0.0

Składnia jest następująca:

npm run <command> [-- <args>]

Zwróć uwagę na konieczne --. Konieczne jest oddzielenie parametrów przekazywanych do npmsamego polecenia i parametrów przekazywanych do skryptu.

Więc jeśli masz package.json

"scripts": {
    "grunt": "grunt",
    "server": "node server.js"
}

Wtedy następujące polecenia byłyby równoważne:

grunt task:target => npm run grunt -- task:target

node server.js --port=1337 => npm run server -- --port=1337

Aby uzyskać wartość parametru, zobacz to pytanie . Do odczytu nazwanych parametrów najlepiej użyć biblioteki parsującej, takiej jak yargs lub minimalist ; nodejs udostępnia process.argvglobalnie, zawierający wartości parametrów wiersza poleceń, ale jest to interfejs API niskiego poziomu (tablica ciągów znaków oddzielona spacjami, dostarczana przez system operacyjny do pliku wykonywalnego węzła).


Edytuj 2013.10.03: Obecnie nie jest to możliwe bezpośrednio. Ale jest otwartynpm powiązany problem GitHub, aby zaimplementować zachowanie, o które prosisz. Wydaje się, że konsensus polega na wdrożeniu tego, ale zależy to od rozwiązania innego problemu wcześniej.


Oryginalna odpowiedź: W celu obejścia (choć niezbyt przydatnego) możesz wykonać następujące czynności:

Powiedz, że nazwa twojego pakietu package.jsonto myPackagei ty też

"scripts": {
    "start": "node ./script.js server"
}

Następnie dodaj package.json:

"config": {
    "myPort": "8080"
}

A w twoim script.js:

// defaulting to 8080 in case if script invoked not via "npm run-script" but directly
var port = process.env.npm_package_config_myPort || 8080

W ten sposób domyślnie npm startużyje 8080. Możesz jednak go skonfigurować (wartość zostanie zapisana npmw pamięci wewnętrznej):

npm config set myPackage:myPort 9090

Następnie podczas wywoływania npm startzostanie użyte 9090 (domyślna wartość z package.jsonzostanie zastąpiona).


1
Działa to również doskonale w połączeniu z pakietami takimi jak yargs; wszystkie parametry po --znaku można idealnie przeanalizować w skrypcie.
Thomas

11
AFAIKS, to pozwala tylko dodawać parametry na końcu twoich skryptów .. co jeśli potrzebujesz parametrów w środku?
Spock

108
-- --argscholera, to dziwne, ale w porządku
sierpień

9
@Spock Możesz używać funkcji powłoki. Oto konfiguracja eslint + tslint, której używam, aby zezwalać na przekazywanie niestandardowych argumentów do eslint, dla bezpieczeństwa, za pomocą „npm run lint - -f unix”: „lint”: „f () {eslint -f codeframe $ @. && npm run tslint && echo 'lint clean!';}; f "
ecmanaut

3
Lepszym sposobem ustawienia wartości „myPackage: myPort 9090” jest użycie flagi config dla polecenia „--myPackage: myPort = 9090” - keithcirkel.co.uk/how-to-use-npm-as-a-build -tool
chrismarx

222

Poprosiłeś o możliwość uruchomienia czegoś takiego npm start 8080 . Jest to możliwe bez potrzeby modyfikowania script.jslub konfigurowania plików w następujący sposób.

Na przykład w "scripts"wartości JSON uwzględnij--

"start": "node ./script.js server $PORT"

A następnie z wiersza poleceń:

$ PORT=8080 npm start

Potwierdziłem, że działa to przy użyciu bash i npm 1.4.23. Zauważ, że to obejście nie wymaga rozwiązania problemu 3494 GitHub npm .


19
To działa naprawdę dobrze. Możesz także zrobić coś takiego, node ./script.js server ${PORT:-8080}aby uczynić to opcjonalnym.
graup

7
Wydaje mi się, że nie jestem w stanie tego zrobić w systemie Windows za pomocą git bash. Może ktoś go uruchomił? (to samo polecenie działa na Ubuntu)
Karolis Šarapnickis

3
Hej @graup, to zadziałało dla mnie NODE_PORT=${PORT=8080}(zauważ równość), ale nie: - składnia
MaieonBrix

14
To nie działa na wielu platformach! Np. W systemie Windows polecenie musiałoby być node ./script.js server %PORT%. Rozważ użycie cross-var i cross-env .
Stijn de Witt

Przypadek użycia zmiennych env jako argumentów wydaje mi się dość ograniczony? Może również użyć czegoś takiego jak moduł config do obsługi zmiennych env, wartości domyślnych i konfiguracji w ogóle.
Mr5o1,

93

Możesz także to zrobić:

W package.json:

"scripts": {
    "cool": "./cool.js"
}

W cool.js:

 console.log({ myVar: process.env.npm_config_myVar });

W CLI:

npm --myVar=something run-script cool

Powinien generować:

{ myVar: 'something' }

Aktualizacja: Używając npm 3.10.3, wydaje się, że obniża wielkość process.env.npm_config_zmiennych? Używam również better-npm-run, więc nie jestem pewien, czy to zachowanie domyślne waniliowy lub nie, ale ta odpowiedź jest praca. Zamiast tego process.env.npm_config_myVarspróbujprocess.env.npm_config_myvar


4
Dzięki, to działało dla mnie! Czego mi brakowało, w szczególności przedrostek „npm_config_” do nazwy zmiennej, którą podajesz w wierszu poleceń.
jp093121,

2
To jest źle. process.env.npm_config_myVarzwraca true, a nie wartość.
Karl Morrison,

1
Działa z npm w wersji 6.8.0, ale tylko wtedy, gdy użyłem małych liter dla nazwy zmiennej. wygląda jak Lilke npm zmień go na małe litery
Ofir Meguri

Świetne rozwiązanie, działa z małymi literami na npm
6.5.0

77

odpowiedź jakub.g jest poprawna, jednak przykład użycia chrząknięcia wydaje się nieco skomplikowany.

Więc moja prostsza odpowiedź:

- Wysłanie argumentu wiersza poleceń do skryptu npm

Składnia wysyłania argumentów wiersza poleceń do skryptu npm:

npm run [command] [-- <args>]

Wyobraź sobie, że w naszym pakiecie.json mamy zadanie uruchamiania npm, aby uruchomić serwer deweloperski webpack:

"scripts": {
  "start": "webpack-dev-server --port 5000"
},

Uruchamiamy to z wiersza poleceń za pomocą npm start

Teraz jeśli chcemy przekazać port do skryptu npm:

"scripts": {
  "start": "webpack-dev-server --port process.env.port || 8080"
},

uruchomienie tego i przekazanie portu np. 5000 za pomocą wiersza poleceń wyglądałoby następująco:

npm start --port:5000

- Korzystanie z konfiguracji package.json:

Jak wspomniano jakub.g , możesz alternatywnie ustawić parametry w konfiguracji pliku package.json

"config": {
  "myPort": "5000"
}

"scripts": {
  "start": "webpack-dev-server --port process.env.npm_package_config_myPort || 8080"
},

npm start użyje portu określonego w konfiguracji lub możesz go zastąpić

npm config set myPackage:myPort 3000

- Ustawienie parametru w skrypcie npm

Przykład odczytu zestawu zmiennych w skrypcie npm. W tym przykładzieNODE_ENV

"scripts": {
  "start:prod": "NODE_ENV=prod node server.js",
  "start:dev": "NODE_ENV=dev node server.js"
},

czytaj NODE_ENV w server.js obu prod lub dev

var env = process.env.NODE_ENV || 'prod'

if(env === 'dev'){
    var app = require("./serverDev.js");
} else {
    var app = require("./serverProd.js");
}

5
zwróć uwagę, że składnia taka jak "start:prod": "NODE_ENV=prod node server.js"in package.jsonnie będzie działać w systemie Windows, chyba że użyjesz cross-env
jakub.g

2
Korekta ?: "start": "webpack-dev-server --port process.env.npm_package_config_myPort || 8080" },powinna być "start": "webpack-dev-server --port $npm_package_config_myPort || 8080" },zgodna z moim zastosowaniem wyjaśnionym w tym samouczku . Numer referencyjny procesu może być najwyraźniej używany w javascript.
Aaron Roller,

35

npm 2.x obsługuje argumenty cli

Komenda

npm run-script start -- --foo=3

Package.json

"start": "node ./index.js"

Index.js

console.log('process.argv', process.argv);


1
Akceptowana odpowiedź (podana rok wcześniej niż ta) już wspomina o tej technice.
Dan Dascalescu

34

Użyj process.argvw kodzie, a następnie podaj końcowe $*wartości wpisu skryptu.

Na przykład wypróbuj go za pomocą prostego skryptu, który po prostu loguje dostarczone argumenty do standardowego wyjścia echoargs.js:

console.log('arguments: ' + process.argv.slice(2));

package.json:

"scripts": {
    "start": "node echoargs.js $*"
}

Przykłady:

> npm start 1 2 3
arguments: 1,2,3

process.argv[0]jest plikiem wykonywalnym (węzłem), process.argv[1]jest twoim skryptem.

Testowane z npm v5.3.0 i węzłem v8.4.0


Nie działa po dodaniu --argumentów, np. - npm run demo.js --skipdziała, jeśli dodano dodatkowy --, np. -npm run demo.js -- --skip
Shreyas

Czy możesz użyć tej metody bez osobnego echoargs.jspliku skryptu?
Joshua Pinter

@JoshuaPinter echoargs.js to tylko przykład, zredaguję moją odpowiedź, aby to wyjaśnić
Peter

@Peter Tak, ale czy musi to być plik skryptu. Próbuję utworzyć skrypt, który używa adbdo wypychania .dbpliku do emulatora Androida i akceptuje parametr lokalnej ścieżki .dbpliku do wypychania go, co jest pierwszym parametrem parametru adb push. Coś w tym stylu: "db:push": "adb push process.argv.slice(2) /data/data/com.cntral.app/databases/database.db"i chcę to nazwać npm run db:push /Users/joshuapinter/Downloads/updated.db. jakieś pomysły?
Joshua Pinter

21

Jeśli chcesz przekazać argumenty na środek skryptu npm, a nie tylko dołączać je na końcu, to wbudowane zmienne środowiskowe wydają się działać dobrze:

"scripts": {
  "dev": "BABEL_ARGS=-w npm run build && cd lib/server && nodemon index.js",
  "start": "npm run build && node lib/server/index.js",
  "build": "mkdir -p lib && babel $BABEL_ARGS -s inline --stage 0 src -d lib",
},

Tutaj npm run devprzekazuje -wflagę zegarka babelowi, ale npm run startraz uruchamia zwykłą kompilację.


Jak to się nazywa z CLI?
bwobst

@dresdin npm run dev,npm start
TJ

2
Musisz użyć cross-env, aby użyć go w systemie Windows.
fracz

8

W przeszłości korzystałem z tego linera i po pewnym czasie z dala od Node.js musiałem ostatnio spróbować go odkryć. Podobnie do rozwiązania wspomnianego przez @francoisrv, wykorzystuje node_config_*zmienne.

Utwórz następujący minimalny package.jsonplik:

{
  "name": "argument",
  "version": "1.0.0",
  "scripts": {
    "argument": "echo \"The value of --foo is '${npm_config_foo}'\""
  }
}

Uruchom następujące polecenie:

npm run argument --foo=bar

Obserwuj następujące dane wyjściowe:

Wartość --foo to „bar”

Wszystko to jest dobrze udokumentowane w oficjalnej dokumentacji npm:

Uwaga: W Zmienne środowiskowe pozycją wyjaśnia, że zmienne wewnątrz skryptach zachowują się inaczej niż to, co jest określone w dokumentacji. Dotyczy to rozróżniania wielkości liter , a także tego, czy argument jest zdefiniowany spacją czy znakiem równości .

Uwaga: Jeśli używasz argumentu z łącznikami, zostaną one zastąpione znakami podkreślenia w odpowiedniej zmiennej środowiskowej. Na przykład npm run example --foo-bar=bazodpowiada ${npm_config_foo_bar}.

Uwaga: dla użytkowników bez WSL Windows zobaczyć komentarze @Doctor niebieski jest poniżej ... TL; DR zastąpić ${npm_config_foo}z %npm_config_foo%.


Witaj. Próbuję użyć twojego przykładu, ale obawiam się, że to nie działa dla mnie. Kopiować, wklejać swoje „argument” scenariusz, i zrobił to samo dla polecenia run ( npm run argument --foo=bar), ale zmienna nie otrzymuje: "The value of --foo is '${npm_config_foo}'". Działa w systemie Windows 10, jeśli to ma znaczenie, w wersji 6.9.0 programu NPM.
Doktor Blue

@DoctorBlue Ahh racja, Node i Windows nie zawsze działają dobrze ... Ten artykuł może rzucić nieco światła na zmienne środowiskowe w skryptach npm: (polecenia TL; DR przechodzą bezpośrednio do systemu operacyjnego, nawet jeśli zostały uruchomione z innej powłoki) blog .risingstack.com / node-js-windows-10-tutorial /… Nie jestem pewien co do konfiguracji, ale jeśli używasz Git Bash do uruchomienia Node, możesz rozważyć uruchomienie go za pośrednictwem WSL :) docs. microsoft.com/en-us/windows/nodejs/setup-on-wsl2
Andrew Odri

1
Rozgryzłem to. %npm_config_foo%Zamiast tego musiałem użyć . Czysta linia poleceń Windows / PowerShell tutaj. (Nie mam też wyboru.)
Doktor Blue

6

To tak naprawdę nie odpowiada na twoje pytanie, ale zawsze możesz zamiast tego użyć zmiennych środowiskowych:

"scripts": {
    "start": "PORT=3000 node server.js"
}

Następnie w pliku server.js:

var port = process.env.PORT || 3000;

1
Jest to dobre, dopóki jesteś na platformie Unix. Niestety nie działa z systemem Windows, ponieważ ma własną konwencję.
Juho Vepsäläinen

6

Większość powyższych odpowiedzi obejmuje tylko przekazywanie argumentów do skryptu NodeJS, wywoływanego przez npm. Moje rozwiązanie jest do ogólnego użytku.

Wystarczy owinąć skrypt npm shwywołaniem interpretera powłoki (np. ) I przekazać argumenty jak zwykle. Jedynym wyjątkiem jest to, że pierwszy numer argumentu to 0.

Na przykład, chcesz dodać skrypt npm someprogram --env=<argument_1>, w którym someprogrampo prostu wypisuje wartość envargumentu:

pakiet.json

"scripts": {
  "command": "sh -c 'someprogram --env=$0'"
}

Po uruchomieniu:

% npm run -s command my-environment
my-environment

Dzięki! To było idealne!
Felipe Desiderati

Prosty i elegancki! nie będzie działać na powłoce MS DOS.
n370

3

Z tego, co widzę, ludzie używają skryptów package.json, kiedy chcieliby uruchomić skrypt w prostszy sposób. Na przykład, aby użyć nodemontego zainstalowanego w lokalnym module_węzła, nie możemy wywoływać nodemonbezpośrednio z cli, ale możemy to wywoływać za pomocą ./node_modules/nodemon/nodemon.js. Aby uprościć to długie pisanie, możemy umieścić to ...

    ...

    skrypty: {
      „start”: „nodemon app.js”
    }

    ...

... następnie wywołaj, npm startaby użyć „nodemon”, który ma app.js jako pierwszy argument.

Co próbuję powiedzieć, jeśli chcesz po prostu uruchomić serwer za pomocą nodepolecenia, nie sądzę, że musisz go używać scripts. Pisanie na klawiaturze npm startlub node app.jsma taki sam wysiłek.

Ale jeśli chcesz użyć nodemoni chcesz przekazać argument dynamiczny, nie używaj scriptżadnego z nich. Zamiast tego spróbuj użyć dowiązania symbolicznego.

Na przykład za pomocą migracji z sequelize. Tworzę dowiązanie symboliczne ...

ln -s node_modules/sequelize/bin/sequelize sequelize

... I mogę przekazać wszelkie argumenty, gdy je nazywam ...

./sequlize -h /* show help */

./sequelize -m /* upgrade migration */

./sequelize -m -u /* downgrade migration */

itp...

W tym momencie użycie dowiązania symbolicznego to najlepszy sposób, jaki mógłbym wymyślić, ale tak naprawdę nie sądzę, że jest to najlepsza praktyka.

Mam również nadzieję na twoją opinię do mojej odpowiedzi.


1
To wcale nie odpowiada na pytanie. Nie wiem, jak to zyskało 6 głosów pozytywnych, ale gratulacje :)
Dan Dascalescu

2

Uwaga: To podejście modyfikuje Twoje package.jsonw locie, użyj go, jeśli nie masz alternatywy.

Musiałem przekazać argumenty wiersza poleceń do moich skryptów, które były jak:

"scripts": {
    "start": "npm run build && npm run watch",
    "watch": "concurrently  \"npm run watch-ts\" \"npm run watch-node\"",
    ...
}

Oznacza to, że uruchamiam aplikację npm run start.

Jeśli chcę przekazać jakieś argumenty, zacznę od:

npm run start -- --config=someConfig

Co to robi to: npm run build && npm run watch -- --config=someConfig. Problem w tym, że zawsze dołącza argumenty na końcu skryptu. Oznacza to, że wszystkie powiązane łańcuchy skryptów nie otrzymują tych argumentów (Argumenty mogą, ale nie muszą być wymagane przez wszystkich, ale to inna historia). Ponadto po wywołaniu połączonych skryptów te skrypty nie otrzymają przekazanych argumentów. tj. watchSkrypt nie otrzyma przekazanych argumentów.

Wykorzystanie produkcyjne mojej aplikacji jest takie .exe, więc przekazywanie argumentów w exe działa dobrze, ale jeśli chcesz to zrobić podczas programowania, staje się problamatyczne.

Nie mogłem znaleźć odpowiedniego sposobu, aby to osiągnąć, więc właśnie tego próbowałem.

Utworzyłem plik javascript: start-script.jsna poziomie nadrzędnym aplikacji mam „default.package.json” i zamiast utrzymywać „package.json”, utrzymuję „default.package.json”. Celem start-script.jsonjest odczytanie default.package.json, wyodrębnienie scriptsi poszukiwanie, npm run scriptnamea następnie dołączenie przekazanych argumentów do tych skryptów. Następnie utworzy nowy package.jsoni skopiuje dane z pliku default.package.json ze zmodyfikowanymi skryptami, a następnie wywoła npm run start.

const fs = require('fs');
const { spawn } = require('child_process');

// open default.package.json
const defaultPackage = fs.readFileSync('./default.package.json');
try {
    const packageOb = JSON.parse(defaultPackage);
    // loop over the scripts present in this object, edit them with flags
    if ('scripts' in packageOb && process.argv.length > 2) {

        const passedFlags = ` -- ${process.argv.slice(2).join(' ')}`;
        // assuming the script names have words, : or -, modify the regex if required.
        const regexPattern = /(npm run [\w:-]*)/g;
        const scriptsWithFlags = Object.entries(packageOb.scripts).reduce((acc, [key, value]) => {
            const patternMatches = value.match(regexPattern);
            // loop over all the matched strings and attach the desired flags.
            if (patternMatches) {
                for (let eachMatchedPattern of patternMatches) {
                    const startIndex = value.indexOf(eachMatchedPattern);
                    const endIndex = startIndex + eachMatchedPattern.length;
                    // save the string which doen't fall in this matched pattern range.
                    value = value.slice(0, startIndex) + eachMatchedPattern + passedFlags + value.slice(endIndex);
                }
            }
            acc[key] = value;
            return acc;
        }, {});
        packageOb.scripts = scriptsWithFlags;
    }

    const modifiedJSON = JSON.stringify(packageOb, null, 4);
    fs.writeFileSync('./package.json', modifiedJSON);

    // now run your npm start script
    let cmd = 'npm';
    // check if this works in your OS
    if (process.platform === 'win32') {
        cmd = 'npm.cmd';    // https://github.com/nodejs/node/issues/3675
    }
    spawn(cmd, ['run', 'start'], { stdio: 'inherit' });

} catch(e) {
    console.log('Error while parsing default.package.json', e);
}

Teraz, zamiast robić npm run start, robięnode start-script.js --c=somethis --r=somethingElse

Początkowy przebieg wygląda dobrze, ale nie został dokładnie przetestowany. Skorzystaj z niego, jeśli chcesz tworzyć aplikacje.


1

Znalazłem to pytanie, gdy próbowałem rozwiązać mój problem z uruchomieniem sekwencjonowania seed: generuj polecenie cli:

node_modules/.bin/sequelize seed:generate --name=user

Przejdźmy do sedna. Chciałem mieć krótkie polecenie skryptu w moim pliku package.json i jednocześnie podać argument --name

Odpowiedź nadeszła po kilku eksperymentach. Oto moje polecenie w pakiecie.json

"scripts: {
  "seed:generate":"NODE_ENV=development node_modules/.bin/sequelize seed:generate"
}

... a oto przykład uruchomienia go w terminalu w celu wygenerowania pliku źródłowego dla użytkownika

> yarn seed:generate --name=user

> npm run seed:generate -- --name=user

Do Twojej wiadomości

yarn -v
1.6.0

npm -v
5.6.0

2
Czy jest to ta sama technika, którą objaśniono w przyjętej odpowiedzi z 2013 r. -- --arg1, ...?
Dan Dascalescu

2
OK, więc po co powtarzać odpowiedź?
Dan Dascalescu

Podałem wyraźny przykład użycia, czy to nie oczywiste?
Serge Seletskyy

2
Gdybym chciał podzielić się innym przykładem techniki wyjaśnionej już w innej odpowiedzi, dodam mój przykład jako komentarz do tej odpowiedzi.
Dan Dascalescu

1
gotcha, zrobi to w ten sposób następnym razem
Serge Seletskyy

0

npm run script_target - <argument> Zasadniczo jest to sposób przekazywania argumentów wiersza poleceń, ale będzie działał tylko w przypadku, gdy skrypt ma tylko jedno polecenie uruchomione, tak jak uruchamiam polecenie, tj. npm run start - 4200

"script":{
       "start" : "ng serve --port="
 }

Będzie to działać w celu przekazania parametrów wiersza poleceń, ale co jeśli uruchomimy więcej niż jedno polecenie razem, np. Npm uruchom build c: / workspace / file

"script":{
       "build" : "copy c:/file <arg> && ng build"
 } 

ale interpretuje to w ten sposób podczas uruchamiania kopii c: / file && ng build c: / work space / file i spodziewamy się czegoś takiego jak ta kopia c: / file c: / work space / file && ng build

Uwaga: - więc oczekiwany parametr wiersza polecenia działa tylko w przypadku jednego polecenia w skrypcie.

Przeczytałem kilka odpowiedzi powyżej, w których niektóre z nich piszą, że można uzyskać dostęp do parametru wiersza polecenia za pomocą symbolu $, ale to nie zadziała


0

Wiem, że jest już zatwierdzona odpowiedź, ale podoba mi się to podejście JSON.

npm start '{"PROJECT_NAME_STR":"my amazing stuff", "CRAZY_ARR":[0,7,"hungry"], "MAGICAL_NUMBER_INT": 42, "THING_BOO":true}';

Zazwyczaj potrzebuję 1 var, którego potrzebuję, na przykład nazwy projektu, więc uważam to za szybkie i proste.

Często też mam coś takiego w pakiecie.json

"scripts": {
    "start": "NODE_ENV=development node local.js"
}

Będąc chciwym, chcę „wszystkiego”, NODE_ENV i argumentów z linii CMD.

Po prostu uzyskujesz dostęp do takich rzeczy w swoim pliku (w moim przypadku local.js)

console.log(process.env.NODE_ENV, starter_obj.CRAZY_ARR, starter_obj.PROJECT_NAME_STR, starter_obj.MAGICAL_NUMBER_INT, starter_obj.THING_BOO);

Musisz tylko mieć ten bit powyżej (korzystam z wersji 10.16.0 btw)

var starter_obj = JSON.parse(JSON.parse(process.env.npm_config_argv).remain[0]);

Anyhoo, pytanie już odpowiedział. Myślałem, że podzielę się, ponieważ często używam tej metody.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.