install.cmd, install_dev.cmd and run.cmd hand over to PowerShell scripts that place Node of the .node-version major under %LOCALAPPDATA%, checked against the SHA-256 that nodejs.org publishes, then run npm ci: no administrator right is needed. The scripts stay within Windows PowerShell 5.1, shipped with Windows, and carry a UTF-8 byte order mark, without which 5.1 reads them in the system code page and mangles accents. The licence guard now reads .ps1, .cmd, .nix and Makefile. Checked: parsed and run under pwsh in a container, Node for win-x64 downloaded, verified and extracted; not yet run on Windows itself. --- FR --- [ADD] scripts : installer et lancer sous Windows, par PowerShell 5.1 install.cmd, install_dev.cmd et run.cmd passent la main à des scripts PowerShell qui posent Node de la majeure de .node-version sous %LOCALAPPDATA%, vérifié contre la somme SHA-256 que publie nodejs.org, puis lancent npm ci : aucun droit d'administrateur n'est requis. Les scripts s'en tiennent à Windows PowerShell 5.1, livré avec Windows, et portent une marque d'ordre d'octets UTF-8, sans laquelle 5.1 les lit dans la page de code du système et défigure les accents. La garde de licence lit désormais les .ps1, .cmd, .nix et le Makefile. Vérifié : analysés et exécutés sous pwsh en conteneur, Node win-x64 téléchargé, vérifié, extrait ; pas encore exécutés sous Windows même. Assisted-by: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
171 lines
7 KiB
PowerShell
171 lines
7 KiB
PowerShell
# © 2026 TechnoLibre (http://www.technolibre.ca)
|
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
|
|
|
# Fonctions communes des scripts Windows : le Node du projet, posé hors du
|
|
# dépôt sous %LOCALAPPDATA%, et la vérification de l'installation. Ce
|
|
# fichier se charge par « . ». Il s'en tient à Windows PowerShell 5.1, livré
|
|
# avec Windows : ni ??, ni ?., ni opérateur ternaire. Il est enregistré en
|
|
# UTF-8 avec marque d'ordre d'octets, sans laquelle Windows PowerShell 5.1 le
|
|
# lit dans la page de code du système et défigure les accents.
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
Set-StrictMode -Version 3.0
|
|
# Sans ces réglages, Windows PowerShell 5.1 écrit les accents dans la page de
|
|
# code de la console, peut refuser TLS 1.2 que nodejs.org exige, et ralentit
|
|
# chaque téléchargement par sa barre de progression.
|
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
[Net.ServicePointManager]::SecurityProtocol =
|
|
[Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
|
|
$ProgressPreference = 'SilentlyContinue'
|
|
|
|
$script:GttRacine = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
|
|
|
|
function Write-Gtt([string]$Message) {
|
|
[Console]::Error.WriteLine($Message)
|
|
}
|
|
|
|
function Stop-Gtt([string]$Message) {
|
|
Write-Gtt "Refusé : $Message"
|
|
exit 1
|
|
}
|
|
|
|
# Majeure de Node que vise le projet ; .node-version en est la seule source.
|
|
function Get-GttMajeure {
|
|
$majeure = (Get-Content -Raw (Join-Path $script:GttRacine '.node-version')).Trim()
|
|
if ($majeure -notmatch '^[0-9]+$') {
|
|
Stop-Gtt ".node-version doit porter une majeure entière : « $majeure »."
|
|
}
|
|
return $majeure
|
|
}
|
|
|
|
# Architecture au sens de nodejs.org. PROCESSOR_ARCHITEW6432 nomme celle du
|
|
# système quand PowerShell lui-même tourne en 32 bits.
|
|
function Get-GttArchNode {
|
|
$arch = $env:PROCESSOR_ARCHITEW6432
|
|
if (-not $arch) { $arch = $env:PROCESSOR_ARCHITECTURE }
|
|
switch ($arch) {
|
|
'AMD64' { return 'x64' }
|
|
'ARM64' { return 'arm64' }
|
|
default { Stop-Gtt "architecture $arch : nodejs.org n'y publie pas Node pour Windows." }
|
|
}
|
|
}
|
|
|
|
function Get-GttDossierDonnees {
|
|
return (Join-Path $env:LOCALAPPDATA 'gestion_table_tournante_libre')
|
|
}
|
|
|
|
function Get-GttDossierNode {
|
|
return (Join-Path (Get-GttDossierDonnees) ('node-{0}-win-{1}' -f (Get-GttMajeure), (Get-GttArchNode)))
|
|
}
|
|
|
|
# Somme et nom de l'archive .zip d'une architecture dans les lignes d'un
|
|
# SHASUMS256.txt de nodejs.org ; $null quand elle n'y figure pas.
|
|
function Find-GttArchiveNode([string[]]$Lignes, [string]$Arch) {
|
|
$suffixe = "-win-$Arch.zip"
|
|
foreach ($ligne in $Lignes) {
|
|
$champs = @($ligne.Trim() -split '\s+')
|
|
if ($champs.Count -ge 2 -and $champs[1] -match '^node-v[0-9]+\.[0-9]+\.[0-9]+-' -and
|
|
$champs[1].EndsWith($suffixe, [StringComparison]::Ordinal)) {
|
|
return @($champs[0], $champs[1])
|
|
}
|
|
}
|
|
return $null
|
|
}
|
|
|
|
# Pose la dernière version publiée de la majeure voulue, une fois. L'archive
|
|
# est vérifiée contre la somme que publie nodejs.org, puis extraite à côté de
|
|
# la cible et renommée : une extraction interrompue ne laisse jamais une
|
|
# cible à moitié écrite.
|
|
function Install-GttNode {
|
|
$majeure = Get-GttMajeure
|
|
$arch = Get-GttArchNode
|
|
$cible = Get-GttDossierNode
|
|
$node = Join-Path $cible 'node.exe'
|
|
if (Test-Path $node) {
|
|
Write-Gtt ('Node {0} : {1}' -f (& $node --version), $cible)
|
|
return
|
|
}
|
|
$cache = Join-Path (Get-GttDossierDonnees) 'cache'
|
|
New-Item -ItemType Directory -Force -Path $cache | Out-Null
|
|
$base = "https://nodejs.org/dist/latest-v$majeure.x"
|
|
$sommes = Join-Path $cache "SHASUMS256-v$majeure.txt"
|
|
Invoke-WebRequest -UseBasicParsing -Uri "$base/SHASUMS256.txt" -OutFile $sommes
|
|
$trouve = Find-GttArchiveNode (Get-Content $sommes) $arch
|
|
if (-not $trouve) { Stop-Gtt "nodejs.org ne publie pas Node $majeure pour win-$arch." }
|
|
$somme = $trouve[0]
|
|
$nom = $trouve[1]
|
|
$archive = Join-Path $cache $nom
|
|
if (-not (Test-Path $archive) -or (Get-FileHash -Algorithm SHA256 $archive).Hash -ne $somme) {
|
|
Write-Gtt "Téléchargement de $nom"
|
|
$partiel = "$archive.$PID.partiel"
|
|
Invoke-WebRequest -UseBasicParsing -Uri "$base/$nom" -OutFile $partiel
|
|
if ((Get-FileHash -Algorithm SHA256 $partiel).Hash -ne $somme) {
|
|
Remove-Item -Force $partiel
|
|
Stop-Gtt "$nom : la somme SHA-256 ne correspond pas à celle que publie nodejs.org."
|
|
}
|
|
Move-Item -Force $partiel $archive
|
|
}
|
|
$tmp = Join-Path (Get-GttDossierDonnees) ('extraction.' + [guid]::NewGuid().ToString('N'))
|
|
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
|
[System.IO.Compression.ZipFile]::ExtractToDirectory($archive, $tmp)
|
|
if (Test-Path $cible) { Remove-Item -Recurse -Force $cible }
|
|
Move-Item (Join-Path $tmp ([IO.Path]::GetFileNameWithoutExtension($nom))) $cible
|
|
Remove-Item -Force $tmp
|
|
Write-Gtt ('Node {0} posé : {1}' -f (& $node --version), $cible)
|
|
}
|
|
|
|
# Met le Node du projet en tête du PATH de la séance ; faux quand il n'est
|
|
# pas posé.
|
|
function Enable-GttNode {
|
|
$cible = Get-GttDossierNode
|
|
if (-not (Test-Path (Join-Path $cible 'node.exe'))) { return $false }
|
|
$env:Path = "$cible;$env:Path"
|
|
return $true
|
|
}
|
|
|
|
# Vrai quand les dépendances npm suivent package-lock.json : npm écrit
|
|
# node_modules\.package-lock.json à chaque installation.
|
|
function Test-GttDependances {
|
|
$recu = Join-Path $script:GttRacine 'node_modules\.package-lock.json'
|
|
if (-not (Test-Path $recu)) { return $false }
|
|
$verrou = Get-Item (Join-Path $script:GttRacine 'package-lock.json')
|
|
return ($verrou.LastWriteTimeUtc -le (Get-Item $recu).LastWriteTimeUtc)
|
|
}
|
|
|
|
# Vrai sur une réponse affirmative — o, oui, y, yes. Le défaut est non, et
|
|
# une entrée redirigée ne consent à rien. GTT_OUI=1 répond oui d'avance.
|
|
function Confirm-Gtt([string]$Question) {
|
|
if ($env:GTT_OUI -eq '1') { return $true }
|
|
if ([Console]::IsInputRedirected) { return $false }
|
|
$reponse = Read-Host "$Question [o/N]"
|
|
return (@('o', 'oui', 'y', 'yes') -contains $reponse.Trim().ToLowerInvariant())
|
|
}
|
|
|
|
# Exécute un programme et refuse quand il échoue, en le nommant.
|
|
function Invoke-Gtt([string]$Programme, [string[]]$Arguments) {
|
|
& $Programme @Arguments
|
|
if ($LASTEXITCODE -ne 0) { Stop-Gtt "$Programme $($Arguments -join ' ') échoue (code $LASTEXITCODE)." }
|
|
}
|
|
|
|
function Invoke-GttNpmCi {
|
|
Invoke-Gtt 'npm.cmd' @('ci', '--no-audit', '--no-fund')
|
|
}
|
|
|
|
# Rend prêt à exécuter : le Node du projet en tête du PATH, et des
|
|
# dépendances npm qui suivent package-lock.json. Node absent, il propose
|
|
# install.cmd ; des dépendances en retard se réinstallent sans question.
|
|
function Initialize-Gtt {
|
|
if (-not (Enable-GttNode)) {
|
|
Write-Gtt "Node $(Get-GttMajeure) n'est pas posé."
|
|
if (-not (Confirm-Gtt 'Lancer install.cmd (Node, dépendances npm) ?')) {
|
|
Stop-Gtt 'installation non lancée. Lancer install.cmd, puis recommencer.'
|
|
}
|
|
& (Join-Path $PSScriptRoot 'installer.ps1')
|
|
if (-not (Enable-GttNode)) { Stop-Gtt "Node $(Get-GttMajeure) reste introuvable après l'installation." }
|
|
}
|
|
if (-not (Test-GttDependances)) {
|
|
Write-Gtt 'Les dépendances npm ne suivent pas package-lock.json : npm ci.'
|
|
Push-Location $script:GttRacine
|
|
try { Invoke-GttNpmCi } finally { Pop-Location }
|
|
}
|
|
}
|