Escena Viva és ja 1.0.0, amb la seva etiqueta v1.0.0 a git, i el seu package.json està gairebé complet: nom, versió, main, engines, dependencies, devDependencies. Queda un buit, i és el que més es fa servir en el dia a dia: "scripts": { }. Aquest objecte buit és avui el motiu que, per arrencar el servidor, algú hagi de recordar node src/servidor/servidor.js; per llançar l'informe d'ocupació, node src/informes/ocupacio.js; i per a la canonada de vendes, una ruta que només coneix qui la va escriure.

En aquesta lliçó convertim scripts en la interfície única del projecte. Quan acabem, qualsevol persona que cloni el repositori podrà escriure npm run i veure la llista completa de coses que pot fer, sense obrir cap README ni preguntar a ningú. I de passada resoldrem la pregunta que va quedar pendent a la lliçó 05-02: per què "lint": "eslint src" funciona encara que no hagis instal·lat mai ESLint globalment.

Contingut

  1. Què és realment el camp scripts
  2. Scripts predefinits davant de scripts personalitzats
  3. Els scripts d'Escena Viva, un a un
  4. El PATH estès: node_modules/.bin
  5. Pas d'arguments amb --
  6. Variables d'entorn dels scripts
  7. Ganxos pre i post (i l'advertència de postinstall)
  8. Encadenar ordres i sobreviure a Windows
  9. ESLint i Prettier: què resol cadascun
  10. Errors comuns i consells
  11. Exercicis
  12. Conclusió

  1. Què és realment el camp scripts

scripts és un objecte on cada clau és un nom i cada valor és una línia de shell. Quan executes npm run <nom>, npm:

  1. Busca la clau <nom> al package.json del directori actual.
  2. Prepara un entorn especial (variables npm_* i un PATH ampliat, ja ho veurem).
  3. Llança aquesta línia amb el shell del sistema (/bin/sh a POSIX, cmd.exe a Windows tret de configuració diferent).
  4. Retorna el codi de sortida de l'ordre: 0 és èxit, qualsevol altre és fallada.

No hi ha màgia addicional. No és un llenguatge nou: és shell. El valuós no és el mecanisme, sinó la convenció: tothom espera trobar-hi les ordres del projecte.

Ordre Què fa
npm run Llista tots els scripts disponibles amb el seu contingut
npm run <nom> Executa l'script <nom>
npm run <nom> -- <args> Executa l'script passant-li arguments extra
npm start Drecera de npm run start
npm test Drecera de npm run test
npm run <nom> --silent Executa amagant l'eco de npm

  1. Scripts predefinits davant de scripts personalitzats

npm reconeix un grapat de noms predefinits que es poden invocar sense la paraula run:

Nom Invocació curta Comportament especial
start npm start Si no el defineixes, npm intenta node server.js
test npm test Si no el defineixes, falla amb un avís
stop npm stop Sense valor per defecte
restart npm restart Executa stop, després restart, després start

Tota la resta és personalitzat i exigeix npm run. És a dir: npm dev no existeix (npm intentarà interpretar-ho com una subordre seva i fallarà), cal escriure npm run dev.

Un detall històric que continua viu: si no defineixes start, npm busca server.js a l'arrel. Escena Viva té el seu servidor a src/servidor/servidor.js, així que el valor per defecte no ens serveix i l'hem de declarar explícitament. És el correcte de totes maneres: l'script explícit documenta el punt d'entrada.

  1. Els scripts d'Escena Viva, un a un

Aquest és el package.json complet després d'aquesta lliçó:

{
  "name": "escena-viva",
  "version": "1.0.0",
  "description": "Plataforma de venda d'entrades per a esdeveniments culturals",
  "main": "src/servidor/servidor.js",
  "type": "commonjs",
  "private": true,
  "engines": { "node": ">=24.5.0 <25" },
  "scripts": {
    "start": "node src/servidor/servidor.js",
    "dev": "node --watch src/servidor/servidor.js",
    "cataleg": "node src/cataleg.js",
    "informe": "node src/informes/ocupacio.js",
    "vendes": "node src/informes/canonada-vendes.js",
    "lint": "eslint src",
    "format": "prettier --write \"src/**/*.js\"",
    "format:check": "prettier --check \"src/**/*.js\"",
    "comprovar": "npm run lint && npm run format:check",
    "test": "node -e \"console.error('Sense proves encara — Modul 9'); process.exit(1)\""
  },
  "dependencies": { "dotenv": "^17.2.1" },
  "devDependencies": { "prettier": "^3.6.2" }
}

Anem ordre a ordre.

start

"start": "node src/servidor/servidor.js"

Arrenca el servidor HTTP que vam construir al Mòdul 4. Coincideix amb el camp main, i això no és casualitat: main declara el punt d'entrada del paquet, start declara com s'executa el procés. Un servidor en producció s'aixeca amb npm start (o directament amb node, veges l'avís més avall).

dev

"dev": "node --watch src/servidor/servidor.js"

--watch és un indicador natiu de Node (estable des de Node 22) que reinicia el procés quan canvia qualsevol fitxer del qual depengui el mòdul carregat. Durant anys això exigia instal·lar nodemon com a dependència de desenvolupament; avui no cal cap dependència externa, cosa que encaixa amb la filosofia d'Escena Viva: zero dependències allà on la plataforma ja serveix.

Diferències que convé conèixer:

Aspecte node --watch nodemon
Instal·lació Cap, ve amb Node Dependència de desenvolupament
Què vigila El graf de mòduls carregat Patrons configurables de fitxers
Configuració --watch-path, --watch-preserve-output nodemon.json amb regles riques
Altres llenguatges No Sí (pot reiniciar qualsevol ordre)

Per al nostre cas, --watch sobra i basta. Si algun dia necessites vigilar també dades/esdeveniments.json, afegeix --watch-path=./dades.

cataleg

"cataleg": "node src/cataleg.js"

La CLI del catàleg del Mòdul 1: imprimeix per stdout els tres esdeveniments amb les seves sessions. Posar-la a scripts la converteix en part de la interfície del projecte en comptes d'un fitxer solt que cal descobrir llegint l'arbre.

informe i vendes

"informe": "node src/informes/ocupacio.js",
"vendes": "node src/informes/canonada-vendes.js"

informe calcula l'ocupació (7 sessions, aforament 3000, 1811 venudes). vendes llança la canonada de streams que llegeix dades/vendes.csv i produeix l'agregat per sessió. Tots dos escriuen dades per stdout i diagnòstics per stderr, la convenció del curs, així que això continua funcionant:

npm run informe --silent > informe-ocupacio.txt

El --silent és important aquí: sense ell, npm imprimeix dues línies de capçalera (> [email protected] informe i l'ordre) que s'esmunyirien dins del teu fitxer. npm les envia a stdout, no a stderr, així que si un script està pensat per redirigir-se, acostuma't a --silent (o la seva forma curta -s).

lint

"lint": "eslint src"

Aquesta és la promesa que vam fer a 05-02. ESLint no està instal·lat globalment i tanmateix l'script funcionarà tan bon punt afegeixis ESLint a devDependencies. L'explicació completa és a l'apartat 4.

format i format:check

"format": "prettier --write \"src/**/*.js\"",
"format:check": "prettier --check \"src/**/*.js\""

--write reescriu els fitxers; --check només comprova i falla si alguna cosa no està formatada. El primer és per al desenvolupament, el segon per a la integració contínua, on mai no vols que una màquina modifiqui fitxers: vols que et digui que estan malament.

Fixa't en les cometes al voltant del patró. Sense elles, el shell POSIX expandiria src/**/*.js abans que Prettier el veiés, i el resultat dependria de la configuració de globstar del shell i del sistema operatiu. Amb cometes, el patró arriba intacte a Prettier, que l'expandeix ell mateix de forma idèntica a totes les plataformes. És un detall petit amb conseqüències reals.

Els dos punts de format:check no signifiquen res per a npm: és només una convenció visual per agrupar variants d'un mateix script. npm run format:check és un nom com qualsevol altre.

comprovar

"comprovar": "npm run lint && npm run format:check"

Un script de composició: no fa feina pròpia, encadena dos més. Aquest és el patró que convé interioritzar: scripts petits d'un sol propòsit, més scripts que els combinen. Si demà hi afegim comprovació de tipus, s'afegeix a la cadena sense tocar els altres.

test

"test": "node -e \"console.error('Sense proves encara — Modul 9'); process.exit(1)\""

Un test honest. Hi ha dues males alternatives:

  • Deixar el test per defecte de npm (echo \"Error: no test specified\" && exit 1), que no diu res útil.
  • Posar "test": "exit 0" perquè la CI passi en verd. Això és mentir: el semàfor verd deixa de significar alguna cosa.

El nostre script escriu el motiu per stderr i surt amb codi 1. La CI fallarà, que és exactament el que ha de passar en un projecte 1.0.0 sense proves, i el missatge diu on és la solució. Al Mòdul 9 aquest script passarà a ser node --test test/ i el vermell es tornarà verd amb raó.

  1. El PATH estès: node_modules/.bin

Aquí hi ha la resposta promesa. Molts paquets declaren executables al seu propi package.json:

{
  "name": "prettier",
  "bin": { "prettier": "./bin/prettier.cjs" }
}

Quan npm instal·la un paquet amb camp bin, crea un enllaç a node_modules/.bin/. Després d'instal·lar Prettier, el projecte té:

ls node_modules/.bin
# prettier

I quan executes npm run <alguna-cosa>, npm anteposa aquest directori al PATH del procés fill. Pots comprovar-ho tu mateix:

"on": "node -e \"console.log(process.env.PATH.split(':')[0])\""
npm run on --silent
# /home/el-teu-usuari/escena-viva/node_modules/.bin

Per això "format": "prettier --write ..." troba prettier sense instal·lació global i sense escriure ./node_modules/.bin/prettier. I per això "lint": "eslint src" funcionarà tan bon punt ESLint sigui a devDependencies.

flowchart LR
  A["npm run format"] --> B["PATH = node_modules/.bin : PATH original"]
  B --> C["el shell busca 'prettier'"]
  C --> D["node_modules/.bin/prettier"]
  D --> E["node_modules/prettier/bin/prettier.cjs"]

Tres conseqüències pràctiques:

  • La versió que s'executa és la del projecte, no la del sistema. Dos projectes amb Prettier 2 i Prettier 3 conviuen sense conflictes.
  • No cal npx dins dels scripts. npx prettier funciona, però afegeix una resolució innecessària. Dins de scripts, escriu el binari a seques.
  • Si una ordre falla amb command not found dins d'un script, gairebé sempre significa que falta el paquet a les dependències, no que falti una instal·lació global.

  1. Pas d'arguments amb --

Això no fa el que sembla:

npm run cataleg --sala="Sala Boveda"

npm interpreta --sala com una opció seva (que no coneix) i no la passa a l'script. El separador -- marca la frontera:

npm run cataleg -- --sala="Sala Boveda"

Ara l'ordre executada és node src/cataleg.js --sala="Sala Boveda" i els arguments arriben a process.argv. Amb un script així:

'use strict';

// Llegeix --sala de l-argv; retorna null si no s-ha passat.
function llegirSalaDeParametres(parametres) {
  const trobat = parametres.find((arg) => arg.startsWith('--sala='));
  return trobat ? trobat.slice('--sala='.length) : null;
}

module.exports = { llegirSalaDeParametres };

El filtre funciona igual el llancis amb node directament o amb npm run ... --. Regla mnemotècnica: tot el que va abans de -- és per a npm; tot el que va després, per al teu programa.

  1. Variables d'entorn dels scripts

npm injecta al procés fill un conjunt de variables derivades del manifest i de la configuració:

Variable Contingut
npm_package_name escena-viva
npm_package_version 1.0.0
npm_lifecycle_event Nom de l'script en execució
npm_config_* Qualsevol opció de configuració de npm
PATH Estès amb node_modules/.bin

Un ús real: que el servidor registri la seva versió en arrencar sense haver de llegir el package.json en temps d'execució.

// Dins de src/servidor/servidor.js, en arrencar.
const versio = process.env.npm_package_version ?? 'desconeguda';
process.stderr.write(`Escena Viva ${versio} escoltant a ${HOST}:${PORT}\n`);

Compte amb la trampa: si algú arrenca amb node src/servidor/servidor.js en comptes de npm start, aquesta variable no existeix. Per això el ?? amb un valor de reserva. Mai no facis dependre la lògica de negoci d'una variable npm_*; fes-les servir només per a diagnòstics.

npm_lifecycle_event permet que un mateix fitxer es comporti diferent segons com l'hagin cridat, tot i que sol ser preferible tenir dos scripts explícits.

Les opcions de configuració també hi arriben. Si executes npm run informe --format=csv, npm exposa npm_config_format=csv. És un mecanisme real, però prefereix -- i process.argv: és explícit, portable i funciona quan executes l'script sense npm.

  1. Ganxos pre i post

Per a qualsevol script x, npm executa automàticament prex abans i postx després, sempre que existeixin. Funciona amb els predefinits i amb els personalitzats:

"prestart": "node -e \"require('node:fs').accessSync('dades/esdeveniments.json')\"",
"start": "node src/servidor/servidor.js"

Si el fitxer de dades no existeix, prestart falla, i npm no executa start. Una fallada primerenca i clara en lloc d'un servidor arrencat que peta a la primera petició.

Els ganxos més coneguts són els del cicle d'instal·lació:

Ganxo Quan es dispara
preinstall Abans d'instal·lar dependències
install / postinstall Després d'instal·lar-les
prepare Després de npm install local i abans de npm publish
prepublishOnly Només abans de publicar

L'advertència de seguretat

postinstall és potent i per això és perillós. Quan instal·les un paquet, el seu postinstall s'executa a la teva màquina amb els teus permisos, sense preguntar-te res. Pot llegir el teu ~/.npmrc (on viu el teu token de npm), les teves variables d'entorn, les teves claus SSH.

Això no és teoria: és el vector habitual dels atacs a la cadena de subministrament. Un mantenidor amb el compte compromès publica una versió de pedaç amb un postinstall maliciós, i milers de màquines l'executen en hores.

Mesures immediates, que ampliarem a la lliçó 05-06:

  • A CI i producció, npm ci --ignore-scripts quan el projecte ho toleri.
  • Revisar els postinstall de les dependències noves abans d'acceptar-les.
  • No desar secrets a l'entorn de la màquina que instal·la paquets.

Als teus propis scripts, postinstall està bé per a tasques del projecte (crear un directori, generar un fitxer derivat). El problema és el codi aliè, no el mecanisme.

  1. Encadenar ordres i sobreviure a Windows

Els operadors del shell funcionen tal qual:

Operador Significat
a && b Executa b només si a acaba amb codi 0
a || b Executa b només si a falla
a ; b Executa b sempre
a & b Executa tots dos en paral·lel (no a cmd.exe)

&& és el que vols el 95 % de les vegades: encadena i s'atura a la primera fallada. || serveix per a valors de reserva: "informe": "node src/informes/ocupacio.js || echo 'informe no disponible' 1>&2".

La portabilitat és el problema real. Aquestes coses es trenquen a Windows:

Construcció POSIX Problema a Windows Solució
rm -rf dist rm no existeix node:fs o el paquet rimraf
NODE_ENV=produccio node x.js Sintaxi no vàlida a cmd cross-env
cmd1 & cmd2 (paral·lel) & és seqüencial npm-run-all --parallel
cat fitxer | node x.js Comportament diferent Llegir el fitxer des de Node
Cometes simples cmd.exe no les interpreta Cometes dobles escapades

Els nostres scripts d'Escena Viva són deliberadament portables: només invoquen node, eslint i prettier, i fan servir cometes dobles escapades. Si algun dia necessites variables d'entorn en línia:

"dev:verbose": "cross-env NIVELL_LOG=debug node --watch src/servidor/servidor.js"

Regla general: si un script comença a assemblar-se a un programa, escriu-lo com un programa a scripts/ i crida'l amb node. JavaScript és portable; el shell no.

  1. ESLint i Prettier: què resol cadascun

Es confonen sovint i no competeixen:

ESLint Prettier
Pregunta que respon Aquest codi té problemes? Aquest codi està ben presentat?
Detecta Variables sense fer servir, await en bucles, promeses sense capturar Res de semàntic
Modifica Només el que sap arreglar (--fix) Tot el format, sempre
Discutible Sí, cada regla és una decisió d'equip No, aquesta és la gràcia

La configuració moderna és simple: Prettier mana en el format, ESLint mana en la correcció, i es desactiven a ESLint les regles d'estil que xocarien amb Prettier. Amb el format fora de la conversa, les revisions de codi deixen de discutir cometes i parlen del que importa.

Per a Escena Viva, ESLint hi entraria així:

npm install --save-dev eslint

I l'script "lint": "eslint src" comença a funcionar sense res més, gràcies al PATH de l'apartat 4. La configuració de regles és un tema en si mateix i no la desenvolupem aquí: el rellevant per a aquesta lliçó és que l'eina s'invoca des de scripts i viu a devDependencies.

Errors Comuns i Consells

  • Escriure npm dev. Només start, test, stop i restart prescindeixen de run. Per a la resta, npm run dev.
  • Oblidar el -- en passar arguments. npm run cataleg --sala=X no arriba al teu programa; npm run cataleg -- --sala=X sí.
  • Redirigir sense --silent. Les capçaleres de npm van a stdout i contaminen el fitxer de sortida.
  • Posar "test": "exit 0". Un verd fals és pitjor que un vermell honest: destrueix la confiança en la CI.
  • Scripts gegants de cinc ordres encadenades. Divideix en scripts petits i compon amb &&. Es depuren millor i es reutilitzen.
  • Assumir bash. sh no és bash i cmd.exe no és cap dels dos. Si dubtes, mou la lògica a un fitxer .js.
  • Dependre de npm_package_version a la lògica. Només existeix si es va arrencar amb npm. Fes-la servir per a diagnòstics, amb valor de reserva.
  • Consell: executa npm run sense arguments en entrar en un projecte aliè. És la documentació més fiable que trobaràs, perquè si estigués desactualitzada, no funcionaria.
  • Consell: anomena els scripts pel que fan per al negoci (informe, vendes, cataleg), no per l'eina que fan servir. L'eina canviarà; la intenció, no.

Exercicis

Exercici 1: un script verificar-dades amb ganxo

Afegeix a Escena Viva un script verificar-dades que comprovi que dades/esdeveniments.json existeix, és JSON vàlid i conté exactament 3 esdeveniments amb 7 sessions en total. Ha d'imprimir el resum per stdout i sortir amb codi 1 si alguna cosa falla. Enganxa'l perquè s'executi automàticament abans d'informe.

Exercici 2: arguments i --

Modifica src/cataleg.js per acceptar --sala=<nom> i filtrar les sessions d'aquesta sala. Comprova que funciona tant amb node directe com amb npm run. Explica per què una de les dues formes necessita --.

Exercici 3: composició i portabilitat

Dissenya un script publicar-informes que executi comprovar, després informe i després vendes, aturant-se a la primera fallada. Després, revisa aquesta proposta d'un company i digues quines tres coses es trencaran a Windows:

"publicar-informes": "rm -rf sortida && mkdir sortida && NODE_ENV=prod node src/informes/ocupacio.js > sortida/ocupacio.txt"

Solucions

Solució 1

// src/utils/verificar-dades.js
'use strict';

const fs = require('node:fs');
const { RUTA_ESDEVENIMENTS } = require('../config/rutes.js');

const ESDEVENIMENTS_ESPERATS = 3;
const SESSIONS_ESPERADES = 7;

// Llegeix el fitxer llavor i en valida la forma; llanca si algun compte no quadra.
function verificarDades() {
  const cru = fs.readFileSync(RUTA_ESDEVENIMENTS, 'utf8');
  const esdeveniments = JSON.parse(cru);

  if (!Array.isArray(esdeveniments)) {
    throw new Error('esdeveniments.json no conte un array');
  }
  const totalSessions = esdeveniments.reduce((suma, ev) => suma + ev.sessions.length, 0);

  if (esdeveniments.length !== ESDEVENIMENTS_ESPERATS || totalSessions !== SESSIONS_ESPERADES) {
    throw new Error(
      `S-esperaven ${ESDEVENIMENTS_ESPERATS} esdeveniments i ${SESSIONS_ESPERADES} sessions; ` +
        `n-hi ha ${esdeveniments.length} i ${totalSessions}`
    );
  }
  return { esdeveniments: esdeveniments.length, sessions: totalSessions };
}

if (require.main === module) {
  try {
    const resum = verificarDades();
    process.stdout.write(`Dades correctes: ${resum.esdeveniments} esdeveniments, ${resum.sessions} sessions\n`);
  } catch (error) {
    process.stderr.write(`Dades invalides: ${error.message}\n`);
    process.exit(1);
  }
}

module.exports = { verificarDades };
"verificar-dades": "node src/utils/verificar-dades.js",
"preinforme": "npm run verificar-dades --silent"

El ganxo preinforme talla la cadena abans que informe intenti treballar amb dades corruptes.

Solució 2

// Fragment de src/cataleg.js
const filtreSala = llegirSalaDeParametres(process.argv.slice(2));
const sessionsVisibles = filtreSala
  ? sessions.filter((sessio) => sessio.sala === filtreSala)
  : sessions;
node src/cataleg.js --sala="Teatro Almendra"
npm run cataleg -- --sala="Teatro Almendra"

La forma amb npm run necessita -- perquè npm consumeix els arguments que comencen per -- com a opcions pròpies. Amb node no hi ha intermediari: tot el que va després del fitxer va directe a process.argv.

Solució 3

"publicar-informes": "npm run comprovar && npm run informe --silent && npm run vendes --silent"

Els tres problemes de la proposta del company a Windows:

  1. rm -rf no existeix a cmd.exe; cal fer servir node -e "fs.rmSync('sortida', { recursive: true, force: true })" o rimraf.
  2. NODE_ENV=prod ordre no és sintaxi vàlida a cmd.exe; requereix cross-env.
  3. mkdir sortida falla si el directori ja existeix amb comportament diferent segons el shell; convé fs.mkdirSync(..., { recursive: true }).

A més, la redirecció > sense --silent ficaria les capçaleres de npm dins de sortida/ocupacio.txt.

Conclusió

El package.json d'Escena Viva ja no té buits. scripts és avui la interfície única del projecte: npm start aixeca el servidor, npm run dev el recarrega amb node --watch sense dependre de ningú, npm run cataleg, npm run informe i npm run vendes exposen la feina dels mòduls 1 i 3, npm run comprovar compon linting i format, i npm test falla amb un missatge honest que apunta al Mòdul 9.

Pel camí has entès el mecanisme que ho sosté: npm executa shell amb un PATH que comença a node_modules/.bin, i per això les eines del projecte s'invoquen pel seu nom sense instal·lació global. Saps separar els arguments de npm dels teus amb --, aprofitar npm_package_version per a diagnòstics, encadenar amb && sense sacrificar la portabilitat, i desconfiar del postinstall del codi aliè.

A la lliçó següent, Creació i Publicació de Paquets, canviem de banda del taulell. Fins ara hem consumit paquets; ara en publicarem un. Extraurem src/utils/format.js —amb formatarPreu, formatarData i generarCodiEntrada— a un paquet propi, @escena-viva/format, i veurem com es decideix què és API pública amb exports, què es puja realment amb files, com es comprova el tarball amb npm pack abans que sigui tard, i per què despublicar un paquet és gairebé impossible.

Curs de Node.js: De Principiant a Avançat

Mòdul 1: Introducció a Node.js

Mòdul 2: Conceptes Bàsics

Mòdul 3: Sistema de Fitxers i E/S

Mòdul 4: HTTP i Servidors Web

Mòdul 5: NPM i Gestió de Paquets

Mòdul 6: Framework Express.js

Mòdul 7: Bases de Dades i ORMs

Mòdul 8: Autenticació i Autorització

Mòdul 9: Proves i Depuració

Mòdul 10: Temes Avançats

Mòdul 11: Desplegament i DevOps

Mòdul 12: Projectes del Món Real

© Copyright 2026. Tots els drets reservats