Module ypack

Gestionnaire de paquets pour YScript : installer, rechercher, empaqueter et publier des bibliothèques externes (modules require-ables ou simples dossiers/scripts de travail). Cette page couvre l'ensemble du comportement de référence, aussi bien côté bibliothèque que côté galerie.

ypack s'utilise aussi bien comme bibliothèque (require("ypack")) que via la commande CLI yes.exe ypack <commande> ... (un routeur mince au-dessus de la même bibliothèque — voir La commande yes.exe ypack plus bas).

local ypack = require("ypack")

ypack.install("csv-tools")
local manifest = ypack.info("csv-tools")
print(manifest.name, manifest.version)
$ yes.exe ypack install csv-tools
$ yes.exe ypack list

Concepts

Le manifeste ypack.json

Chaque paquet publiable a, à sa racine, un ypack.json :

{
  "name": "csv-tools",
  "version": "1.2.0",
  "description": "Lecture/écriture de fichiers CSV",
  "engines": { "yscript": ">=0.3.0" },
  "dependencies": { "otherpackage": "^1.0.0" },
  "provides": { "module": true, "command": true }
}

name/version ne sont requis que pour un paquet publiable — un ypack.json qui n'a que dependencies est un manifeste consommateur (déclare les dépendances d'un dossier de travail ou d'un script, sans intention de publication ; voir ypack.ensure). ypack ne distingue pas structurellement les deux : c'est la présence de name+version qui fait la différence.

Un paquet publiable a en plus, à sa racine, un point d'entrée : init.yes (module script require-able) ou <name>.dll (module .NET) — jamais les deux — et/ou command.yes (sous-commande CLI), et optionnellement documentation/index.md (voir ypack.doc).

Pour un module .NET dont l'assembly compilée n'est pas déjà nommée <name>.dll (convention .NET habituelle, ex. MaCompagnie.Modules.CsvTools.dll), ajouter "main": "MaCompagnie.Modules.CsvTools.dll" au manifeste : ypack.pack l'ajoute automatiquement à l'archive sous le nom <name>.dll (voir ypack.pack plus bas) — inutile si le fichier s'appelle déjà <name>.dll, auquel cas main est ignoré.

Le champ optionnel "excludes" (liste de motifs glob simplifiés, ex. ["*.log", "tests/fixtures"]) exclut des fichiers/dossiers de l'archive produite par ypack.pack, quel que soit le type de paquet — pratique pour ignorer des fichiers de développement présents dans le dossier source sans avoir à les en sortir. * matche n'importe quelle suite de caractères hors /, ? un caractère hors / ; un motif sans / matche un nom de fichier/dossier à n'importe quelle profondeur, un motif avec / matche le chemin relatif complet depuis la racine du paquet. ypack exclut de toute façon d'office *.pdb/*.deps.json/*.xml/YScript.dll (bruit de build .NET) — excludes s'ajoute à cette liste, ne la remplace pas.

Niveaux d'installation

Un paquet s'installe à l'un de trois niveaux, chacun avec sa propre racine de dossier :

Niveau Racine Portée
current package.currentroot À côté de l'exécutable yes.exe
user package.userroot (défaut) Profil de l'utilisateur courant
global package.globalroot Partagé par tous les utilisateurs de la machine

La résolution d'un paquet installé (ypack.info, ypack.uninstall, ypack.doc, et la vérification de dépendances déjà satisfaites) cherche dans cet ordre — current → user → global — quand aucun niveau n'est précisé, ce qui permet à une installation --current de surcharger localement un paquet déjà présent à un niveau plus large.

Registre local

Chaque racine d'installation a son propre registre, un fichier ypack-lock.json à sa racine (donc <niveau>root>/ypack-lock.json — ex. package.userroot .. "/ypack-lock.json" pour le niveau user) : ypack.list/ypack.info/ypack.uninstall/ypack.update s'appuient dessus plutôt que de re-parcourir tous les manifestes du dossier à chaque appel. Il recense, pour chaque paquet installé à ce niveau, sa version exacte et sa source d'origine (laquelle des sources configurées l'a fourni) — c'est cette information que ypack.update réutilise pour réinstaller depuis la même source plutôt que de re-parcourir la liste configurée.

Un dossier de module présent physiquement sans entrée correspondante dans ce fichier (installation manuelle, copie directe...) est détecté comme tel par ypack.update (voir plus bas), qui refuse de le mettre à jour automatiquement plutôt que de deviner une origine.

Sources

Une source est un dépôt de paquets — soit un dossier local, soit une galerie HTTP (URL http(s)://). ypack.search, ypack.install et ypack.publish sans source explicite utilisent la liste configurée (ypack.sources()), essayée dans l'ordre jusqu'à la première source où le paquet est trouvé.

La configuration des sources vit dans un ypack-config.json :

{
  "sources": [
    "https://yscript.ygrenier.fr",
    { "url": "https://galerie-interne.example.com", "token": "mon-jeton-de-publication" },
    "D:/mes-paquets-locaux"
  ]
}

Une entrée est soit une chaîne (URL ou chemin de dossier), soit une table {url=..., token=...} — le token n'est utile que pour ypack.publish vers cette source (voir plus bas). Emplacement du fichier :

Résolution de version / dépendances

ypack.install accepte un deuxième argument optionnel qui précise la version voulue :

Forme Exemple Signification
(omis) — Dernière version publiée
exacte "1.2.3" Exactement cette version
caret "^1.2.3" Même version majeure, >= 1.2.3 (dernière compatible retenue)
minimum ">=1.2.3" N'importe quelle version >= 1.2.3 (dernière retenue)

Les dépendances déclarées par manifest.dependencies d'un paquet installé sont résolues automatiquement (récursivement) au moment de ypack.install : chaque dépendance manquante est installée, chaque dépendance déjà installée est vérifiée contre sa contrainte. Un conflit — une dépendance déjà installée dans une version qui ne satisfait pas la contrainte demandée — est une erreur explicite, pas une résolution silencieuse (pas de gestion multi-versions d'un même paquet). Une dépendance circulaire (A → B → A) ne boucle pas : A est enregistrée comme installée avant que ses propres dépendances ne soient traitées.

Immutabilité des versions publiées

Une fois name+version publiés sur une source (locale ou HTTP), republier exactement le même couple est refusé (message d'erreur en local, 409 Conflict sur une galerie HTTP) — les versions sont immuables, un consommateur qui a téléchargé une version doit toujours en retrouver le même contenu. Publier un correctif nécessite un nouveau numéro de version.

Vue d'ensemble — API (require("ypack"))

Fonction Description
ypack.VERSION Version du module ypack lui-même (chaîne)
ypack.LEVELS { "current", "user", "global" }
ypack.root(level) Racine de dossier d'un niveau d'installation
ypack.list([level]) Paquets installés (registre local)
ypack.info(name [, level]) Manifeste d'un paquet installé
ypack.uninstall(name [, level]) Désinstalle un paquet
ypack.update(name [, level]) Met à jour un paquet installé depuis sa source d'origine
ypack.installfile(zippath [, level [, force]]) Installe une archive .zip déjà présente sur le disque
ypack.sources() Liste ordonnée des sources configurées
ypack.setsources(sources [, scope]) Écrit la liste ordonnée de sources (utilisateur ou projet)
ypack.search(query [, source]) Recherche un paquet dans les sources configurées (ou une source unique)
ypack.install(name [, versionorconstraint [, level [, source [, force]]]]) Résout et installe un paquet (+ ses dépendances) depuis les sources
ypack.ensure([manifestpath]) Installe les dépendances manquantes d'un manifeste consommateur
ypack.pack(src, destzip) Empaquette un dossier/script en archive .zip
ypack.doc(name [, level]) Documentation (documentation/index.md) d'un paquet
ypack.publish(zippath [, source [, token]]) Publie une archive vers une source

Vue d'ensemble — CLI (yes.exe ypack ...)

Commande Description
version Affiche la version
help Affiche l'aide
list Liste les paquets installés
info <nom> Affiche le manifeste d'un paquet installé
search [requête] Recherche dans les sources configurées
install [nom\|archive.zip] [version] Installe un paquet, une archive, ou les dépendances du projet si omis
uninstall <nom> Désinstalle un paquet
update <nom> Met à jour un paquet installé depuis sa source d'origine
pack <source> <dest.zip> Empaquette un dossier ou un script isolé
doc <nom> Affiche la documentation d'un paquet
publish <archive.zip> Publie une archive vers une source

Options communes, applicables selon la commande (voir chaque section pour le détail) : --current/--user/--global (niveau d'installation), --source <src> (source unique explicite, bypass la liste configurée), --token <jeton> (authentification de publication vers une source HTTP), --force (installation, écrase même une version plus récente déjà installée à cette portée).


ypack.VERSION

Chaîne, version du module ypack lui-même (pas celle des paquets qu'il gère).

ypack.LEVELS

Tableau { "current", "user", "global" }, dans l'ordre de résolution utilisé partout où un niveau n'est pas explicite.

ypack.root(level)

Retourne le chemin de la racine de dossier correspondant à level ("current", "user" ou "global") — lève une erreur si level n'est pas l'une de ces trois valeurs.

print(ypack.root("user"))   --> ex. C:/Users/alice/AppData/Roaming/yeg/yscript/0.3

ypack.list([level])

Retourne un tableau des paquets installés (indices 1..n), chaque entrée : { name, version, source, level } — source est la source d'origine enregistrée lors de l'installation. Sans level, agrège les trois niveaux.

for _, pkg in ipairs(ypack.list()) do
    print(pkg.name, pkg.version, pkg.level)
end

ypack.info(name [, level])

Retourne le manifeste complet (ypack.json décodé) du paquet name installé, augmenté de deux champs : level (niveau où il a été trouvé) et installdir (dossier d'installation). Cherche current → user → global si level est omis.

nil, message si le paquet n'est pas installé à ce niveau (ou à aucun niveau, si level est omis).

local manifest, err = ypack.info("csv-tools")
if manifest then
    print(manifest.name, manifest.version, manifest.installdir)
else
    print("non installé : " .. err)
end

ypack.uninstall(name [, level])

Supprime le dossier d'installation du paquet name et le retire du registre local. nil, message si le paquet n'est pas installé (au niveau donné, ou à aucun niveau si level est omis).

local ok, err = ypack.uninstall("csv-tools")

ypack.update(name [, level])

Met à jour un paquet installé vers la dernière version disponible sur sa source d'origine — celle enregistrée dans le registre local (ypack-lock.json) lors de son installation. Ne re-parcourt jamais la liste des sources configurées : une mise à jour ne peut pas basculer silencieusement l'origine d'un paquet si cette liste a changé depuis l'installation initiale. Cherche current → user → global si level est omis (comme ypack.info).

Retourne manifest, previousversion — comparez manifest.version à previousversion pour savoir si une mise à jour a effectivement eu lieu (identiques : déjà à jour). nil, message si :

local manifest, previousversion = ypack.update("csv-tools")
if manifest then
    if manifest.version == previousversion then
        print("déjà à jour : " .. manifest.version)
    else
        print(previousversion .. " -> " .. manifest.version)
    end
end

ypack.installfile(zippath [, level [, force]])

Installe directement une archive .zip déjà présente sur le disque (pas de résolution depuis une source — utile pour une archive reçue hors galerie). level par défaut "user". Retourne le manifeste installé, ou nil, message (archive absente, ypack.json absent/invalide de l'archive, point d'entrée manquant, ou version déjà installée à ce niveau strictement plus récente que celle de l'archive — voir Garde de version ci-dessous ; passer force=true pour outrepasser).

local manifest, err = ypack.installfile("csv-tools-1.2.0.zip")
local manifest, err = ypack.installfile("csv-tools-1.0.0.zip", "user", true)  -- force un retour en arrière

ypack.sources()

Retourne la liste ordonnée des sources configurées (voir Sources ci-dessus) : celle du projet courant si ypack.json+ypack-config.json coexistent dans le dossier courant, sinon celle de l'utilisateur, sinon { "https://yscript.ygrenier.fr" } par défaut.

for i, src in ipairs(ypack.sources()) do
    print(i, type(src) == "table" and src.url or src)
end

ypack.setsources(sources [, scope])

Écrit sources (une table, même forme que celle retournée par ypack.sources() — chaînes et/ou tables {url=..., token=...}) dans ypack-config.json. scope vaut "user" (défaut, <UserPath>/yeg/yscript/ypack-config.json, créé si absent) ou "project" (ypack-config.json dans le répertoire courant — à utiliser aux côtés d'un ypack.json de projet pour que ypack.sources() le reprenne en priorité, voir Sources ci-dessus). nil, message si sources n'est pas une table, ou en cas d'échec d'écriture.

local ok, err = ypack.setsources({ "https://yscript.ygrenier.fr", "D:/dev/mon-repo/gallery-dir" })
local ok, err = ypack.setsources({ "D:/dev/mon-repo/gallery-dir" }, "project")

ypack.search(query [, source])

Recherche query (sous-chaîne, insensible à la casse côté galerie HTTP ; correspondance sur le nom côté source locale) dans les sources configurées, ou uniquement dans source si fourni (bypass la liste configurée). query vide ou omis liste tous les paquets disponibles.

Retourne un tableau d'entrées { name, version, description, source } (une par paquet, la dernière version publiée). Une source HTTP injoignable ou en erreur ne fait pas échouer la recherche globale — elle contribue simplement zéro résultat.

for _, r in ipairs(ypack.search("csv")) do
    print(r.name, r.version, r.description)
end

ypack.install(name [, versionorconstraint [, level [, source [, force]]]])

Résout name/versionorconstraint (voir Résolution de version ci-dessus) dans source si fourni, sinon dans les sources configurées essayées dans l'ordre ; télécharge/copie l'archive, l'installe dans level (défaut "user"), enregistre le paquet dans le registre local, puis installe récursivement ses dépendances déclarées. Une installation existante du même nom à ce niveau est remplacée — sauf si elle est strictement plus récente que la version résolue (voir Garde de version ci-dessous ; force=true pour outrepasser).

Retourne le manifeste installé, ou nil, message (paquet introuvable dans les sources, conflit de dépendance, archive invalide, checksum invalide sur téléchargement HTTP, garde de version non outrepassée...).

ypack.install("csv-tools")                     -- dernière version
ypack.install("csv-tools", "^1.0.0")            -- contrainte caret
ypack.install("csv-tools", "1.2.0", "global")   -- version exacte, niveau global
ypack.install("csv-tools", nil, "user", "D:/mes-paquets-locaux")  -- source explicite
ypack.install("csv-tools", "1.0.0", "user", nil, true)  -- force un retour en arrière

Garde de version à l'installation

ypack.install/ypack.installfile comparent, avant d'écraser, la version déjà présente au niveau ciblé (lue directement sur le disque, indépendamment du registre local — détecte donc aussi un paquet installé manuellement) à celle qu'ils s'apprêtent à poser. Si la version déjà installée est strictement plus récente (comparaison SemVer sur les composants numériques), l'installation est refusée avec nil, message plutôt que d'écraser silencieusement une version plus récente — jusqu'à ce que l'appelant passe force=true (ou --force côté CLI). Une version égale ou plus ancienne est remplacée normalement, sans force. ypack.ensure/ypack.update n'ont pas besoin de force : ils ne réinstallent que ce qui est manquant ou explicitement plus récent sur la source d'origine.

ypack.ensure([manifestpath])

Installe (niveau "user" par défaut pour les nouvelles, cherche tous les niveaux pour détecter une dépendance déjà présente) chaque dépendance déclarée dans un manifeste consommateur — ypack.json (contenant seulement dependencies) ou script isolé <nom>.ypack.json — non déjà installée dans une version satisfaisant sa contrainte. Idempotent.

Retourne la liste des noms de paquets effectivement (ré)installés au cours de cet appel, ou nil, message.

local installed, err = ypack.ensure()          -- lit ./ypack.json ou l'unique *.ypack.json
local installed, err = ypack.ensure("my-script.ypack.json")

ypack.pack(src, destzip)

Empaquette src (dossier ou fichier .yes) vers l'archive destzip, toujours construite entrée par entrée (jamais de copie/modification de src en place). Deux modes, selon src :

nil, message en cas d'échec (source introuvable, point d'entrée manquant, manifeste consommateur associé introuvable pour un script isolé).

ypack.pack("dist/csv-tools", "csv-tools-1.2.0.zip")   -- paquet publiable
ypack.pack("myproject/", "myproject-bundle.zip")       -- bundle dossier consommateur
ypack.pack("my-script.yes", "my-script-bundle.zip")    -- bundle script isolé

ypack.doc(name [, level])

Retourne le contenu de documentation/index.md du paquet name — cherché d'abord parmi les paquets installés (level, ou tous niveaux si omis), sinon dans les sources configurées (dernière version publiée de chaque source, essayées dans l'ordre). nil, message si introuvable partout, ou si le paquet existe mais n'a pas de documentation.

local content, err = ypack.doc("csv-tools")
if content then print(content) end

ypack.publish(zippath [, source [, token]])

Publie l'archive zippath (doit contenir un ypack.json publiable, name+version) vers source :

Un jeton est requis pour publier vers une source HTTP (--token, ou configuré pour cette source dans ypack-config.json) — jamais requis pour une source en dossier local, où il n'a aucun sens. Le jeton est transmis en en-tête HTTP Authorization: Bearer <jeton> ; sa valeur et sa portée dépendent de la configuration propre à la galerie ciblée (à obtenir auprès de l'administrateur de cette galerie).

Refuse (voir Immutabilité des versions) si name/version sont déjà publiés sur cette source.

true en cas de succès, nil, message sinon.

ypack.publish("csv-tools-1.2.0.zip")                                       -- 1ère source configurée
ypack.publish("csv-tools-1.2.0.zip", "D:/mes-paquets-locaux")              -- source locale explicite
ypack.publish("csv-tools-1.2.0.zip", "https://galerie.example.com", "mon-jeton")

La commande yes.exe ypack

Routeur mince au-dessus de l'API ci-dessus : parse les arguments/flags, appelle la fonction ypack.* correspondante, met en forme le résultat sur la sortie standard (erreur sur la sortie d'erreur, code de sortie 1).

Options communes

Option Commandes concernées Effet
--current / --user / --global list, info, install, uninstall, update, doc Force le niveau d'installation (défaut --user, cf. ypack.LEVELS)
--source <src> search, install, publish Source unique explicite, bypass la liste configurée
--token <jeton> publish Jeton d'authentification pour une source HTTP
--force install Écrase même une version déjà installée plus récente à ce niveau (voir Garde de version)

yes.exe ypack version

Affiche ypack <VERSION>. Commande par défaut (yes.exe ypack sans argument fait de même).

yes.exe ypack help

Affiche la liste des commandes et options disponibles.

yes.exe ypack list

Liste les paquets installés (ypack.list), une ligne <nom> <version> (<niveau>) par paquet. Aucun paquet installé. si le registre est vide.

yes.exe ypack info <nom>

Affiche le manifeste d'un paquet installé (ypack.info) : nom, version, description si présente, dossier d'installation.

yes.exe ypack search [requête]

Recherche (ypack.search) dans les sources configurées (ou --source <src> pour une source unique). Aucun résultat. si rien ne correspond, sinon une ligne <nom> <version> - <description> par résultat.

yes.exe ypack install [nom|archive.zip] [version]

Trois formes, selon l'argument :

Dans les deux dernières formes, une version déjà installée à ce niveau strictement plus récente que celle demandée fait échouer la commande (voir Garde de version) ; ajouter --force pour l'écraser quand même.

$ yes.exe ypack install                     -- dépendances du projet courant
$ yes.exe ypack install csv-tools           -- dernière version
$ yes.exe ypack install csv-tools ^1.0.0    -- contrainte caret
$ yes.exe ypack install csv-tools-1.2.0.zip -- archive locale
$ yes.exe ypack install csv-tools-1.0.0.zip --force  -- écrase une version plus récente déjà installée

yes.exe ypack uninstall <nom>

Désinstalle un paquet (ypack.uninstall).

yes.exe ypack update <nom>

Met à jour un paquet installé (ypack.update) et affiche <nom> <version> : déjà à jour ou <nom> <ancienne> -> <nouvelle> : mis à jour selon le cas.

$ yes.exe ypack update csv-tools
csv-tools 1.2.0 -> 1.3.0 : mis à jour

yes.exe ypack pack <source> <dest.zip>

Empaquette <source> (dossier ou script .yes) vers <dest.zip> (ypack.pack) et affiche Archive créée : <dest.zip>, suivi entre parenthèses d'une description du contenu quand un manifeste a été trouvé — (nom@version) pour un paquet publiable, (scripts + ypack.json) pour un bundle de dossier consommateur, (script.yes + script.ypack.json) pour un bundle de script isolé. La destination doit toujours être fournie explicitement : contrairement à un paquet publiable (nommage déductible <nom>-<version>.zip), un bundle consommateur n'a ni nom ni version à en tirer.

$ yes.exe ypack pack dist/csv-tools csv-tools-1.2.0.zip
Archive créée : csv-tools-1.2.0.zip (csv-tools@1.2.0)

$ yes.exe ypack pack myproject/ myproject-bundle.zip
Archive créée : myproject-bundle.zip (scripts + ypack.json)

yes.exe ypack doc <nom>

Affiche la documentation (ypack.doc) d'un paquet, installé ou disponible dans les sources configurées.

yes.exe ypack publish <archive.zip>

Publie une archive (ypack.publish) vers --source <src> si fourni, sinon la première source configurée. --token <jeton> pour l'authentification vers une source HTTP (sinon celui configuré pour cette source dans ypack-config.json).

$ yes.exe ypack publish csv-tools-1.2.0.zip
$ yes.exe ypack publish csv-tools-1.2.0.zip --source https://galerie.example.com --token mon-jeton

Erreurs

Sauf mention contraire ci-dessus, toutes les fonctions de l'API retournent nil, message en cas d'échec plutôt que de lever une erreur script — cohérent avec le style io.open/zip.open. Les échecs de décodage JSON internes (ypack.json corrompu, réponse de galerie invalide) sont également remontés sous cette forme, jamais laissés se propager comme une erreur script brute.

Côté CLI, un échec écrit le message sur la sortie d'erreur (préfixé ypack: ) et termine le processus avec le code de sortie 1.

Limites connues (v1)