[ADD] scripts: Windows install and run through PowerShell 5.1
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
This commit is contained in:
parent
de46c582f4
commit
67368b719f
9 changed files with 325 additions and 16 deletions
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
* text=auto eol=lf
|
||||
*.cmd text eol=crlf
|
||||
6
install.cmd
Normal file
6
install.cmd
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
@REM © 2026 TechnoLibre (http://www.technolibre.ca)
|
||||
@REM License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
@echo off
|
||||
rem Installe de quoi lancer l'application depuis les sources : scripts\windows\installer.ps1
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0scripts\windows\installer.ps1" %*
|
||||
exit /b %ERRORLEVEL%
|
||||
6
install_dev.cmd
Normal file
6
install_dev.cmd
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
@REM © 2026 TechnoLibre (http://www.technolibre.ca)
|
||||
@REM License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
@echo off
|
||||
rem Installe l'environnement de developpement : scripts\windows\installer.ps1
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0scripts\windows\installer.ps1" -Dev %*
|
||||
exit /b %ERRORLEVEL%
|
||||
6
run.cmd
Normal file
6
run.cmd
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
@REM © 2026 TechnoLibre (http://www.technolibre.ca)
|
||||
@REM License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
@echo off
|
||||
rem Lance l'application depuis les sources : scripts\windows\lancer.ps1
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0scripts\windows\lancer.ps1" %*
|
||||
exit /b %ERRORLEVEL%
|
||||
33
scripts/windows/avec_node.ps1
Normal file
33
scripts/windows/avec_node.ps1
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# © 2026 TechnoLibre (http://www.technolibre.ca)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
|
||||
# Exécute une commande à la racine du projet, le Node du projet en tête du
|
||||
# PATH, une fois l'installation vérifiée : c'est par lui que le Makefile
|
||||
# lance npm, npx et node sous Windows.
|
||||
#
|
||||
# powershell -File scripts\windows\avec_node.ps1 npm test
|
||||
param([Parameter(ValueFromRemainingArguments = $true)][string[]]$Commande)
|
||||
. (Join-Path $PSScriptRoot 'commun.ps1')
|
||||
|
||||
$elements = @($Commande | Where-Object { $_ })
|
||||
if ($elements.Count -eq 0) {
|
||||
Write-Gtt 'usage : scripts\windows\avec_node.ps1 <commande> [argument…]'
|
||||
exit 2
|
||||
}
|
||||
Initialize-Gtt
|
||||
Push-Location $script:GttRacine
|
||||
try {
|
||||
$programme = $elements[0]
|
||||
# npm et npx se lancent par leur .cmd : leur variante .ps1 dépend de la
|
||||
# stratégie d'exécution de la machine.
|
||||
if (@('npm', 'npx') -contains $programme) { $programme = "$programme.cmd" }
|
||||
if ($elements.Count -gt 1) {
|
||||
$reste = $elements[1..($elements.Count - 1)]
|
||||
& $programme @reste
|
||||
} else {
|
||||
& $programme
|
||||
}
|
||||
exit $LASTEXITCODE
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
171
scripts/windows/commun.ps1
Normal file
171
scripts/windows/commun.ps1
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
# © 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 }
|
||||
}
|
||||
}
|
||||
23
scripts/windows/installer.ps1
Normal file
23
scripts/windows/installer.ps1
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# © 2026 TechnoLibre (http://www.technolibre.ca)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
|
||||
# Installe sous Windows de quoi lancer l'application depuis les sources et
|
||||
# jouer les épreuves : Node de la majeure de .node-version, posé sous
|
||||
# %LOCALAPPDATA% sans droits d'administrateur, et les dépendances de
|
||||
# package-lock.json. -Dev ajoute le Chromium de Playwright des épreuves du
|
||||
# navigateur. install.cmd et install_dev.cmd l'appellent.
|
||||
param([switch]$Dev)
|
||||
. (Join-Path $PSScriptRoot 'commun.ps1')
|
||||
|
||||
Install-GttNode
|
||||
[void](Enable-GttNode)
|
||||
Push-Location $script:GttRacine
|
||||
try {
|
||||
Invoke-GttNpmCi
|
||||
if ($Dev) {
|
||||
Invoke-Gtt 'npx.cmd' @('--no-install', 'playwright', 'install', '--only-shell', 'chromium')
|
||||
}
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
Write-Gtt 'Installation terminée. Lancer : run.cmd'
|
||||
38
scripts/windows/lancer.ps1
Normal file
38
scripts/windows/lancer.ps1
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# © 2026 TechnoLibre (http://www.technolibre.ca)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
|
||||
# Lance l'application depuis les sources, sous Windows : la coquille
|
||||
# Electron charge la page que Vite construit dans www\ (§ 13.1). run.cmd
|
||||
# l'appelle, ses options sont celles de run.sh.
|
||||
#
|
||||
# run.cmd Electron
|
||||
# run.cmd --web le serveur de Vite, http://localhost:5173
|
||||
param([Parameter(ValueFromRemainingArguments = $true)][string[]]$Options)
|
||||
. (Join-Path $PSScriptRoot 'commun.ps1')
|
||||
|
||||
$mode = 'electron'
|
||||
foreach ($option in @($Options | Where-Object { $_ })) {
|
||||
switch ($option) {
|
||||
'--web' { $mode = 'web' }
|
||||
'--electron' { $mode = 'electron' }
|
||||
default {
|
||||
Write-Gtt 'usage : run.cmd [--web | --electron]'
|
||||
exit 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Initialize-Gtt
|
||||
Push-Location $script:GttRacine
|
||||
try {
|
||||
if ($mode -eq 'web') {
|
||||
Write-Gtt "Serveur de développement de Vite : http://localhost:5173 (Ctrl+C pour l'arrêter)."
|
||||
& npm.cmd run --silent dev
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
Invoke-Gtt 'npm.cmd' @('run', '--silent', 'build')
|
||||
& npx.cmd --no-install electron .
|
||||
exit $LASTEXITCODE
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
|
|
@ -3,12 +3,13 @@
|
|||
|
||||
// Chaque fichier source du projet s'ouvre sur l'en-tête de licence : la ligne
|
||||
// du titulaire, puis celle de la licence, après un éventuel shebang ou la
|
||||
// déclaration <!doctype html>. Les deux lignes sont des commentaires du
|
||||
// déclaration <!doctype html>, et après la marque d'ordre d'octets que
|
||||
// portent les scripts PowerShell. Les deux lignes sont des commentaires du
|
||||
// langage du fichier ; le texte ne varie pas. package.json déclare la même
|
||||
// licence et le même titulaire, que reprennent les outils d'emballage.
|
||||
import assert from 'node:assert/strict';
|
||||
import { readdirSync, readFileSync } from 'node:fs';
|
||||
import { extname, join, relative, sep } from 'node:path';
|
||||
import { basename, extname, join, relative, sep } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { describe, test } from './lanceur.js';
|
||||
|
||||
|
|
@ -17,8 +18,11 @@ const RACINE = fileURLToPath(new URL('..', import.meta.url));
|
|||
const TITULAIRE = /^© \d{4} TechnoLibre \(http:\/\/www\.technolibre\.ca\)$/;
|
||||
const LICENCE = 'License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)';
|
||||
|
||||
// Marqueurs de commentaire, par extension de fichier source. Un commentaire
|
||||
// de ligne n'a pas de fermant.
|
||||
// Marqueurs de commentaire, par extension de fichier source, ou par nom pour
|
||||
// un fichier sans extension. Un commentaire de ligne n'a pas de fermant. Un
|
||||
// ouvrant est une chaîne qui ouvre la ligne, ou un motif : REM ouvre une
|
||||
// ligne de commandes Windows, précédé ou non de @, quelle que soit sa casse,
|
||||
// et suivi d'un blanc — @REMARQUE est une commande.
|
||||
const COMMENTAIRE = new Map([
|
||||
['.js', { ouvrant: '//', fermant: null }],
|
||||
['.cjs', { ouvrant: '//', fermant: null }],
|
||||
|
|
@ -27,8 +31,16 @@ const COMMENTAIRE = new Map([
|
|||
['.svelte', { ouvrant: '<!--', fermant: '-->' }],
|
||||
['.html', { ouvrant: '<!--', fermant: '-->' }],
|
||||
['.css', { ouvrant: '/*', fermant: '*/' }],
|
||||
['.ps1', { ouvrant: '#', fermant: null }],
|
||||
['.nix', { ouvrant: '#', fermant: null }],
|
||||
['.cmd', { ouvrant: /^@?rem(?=\s)/i, fermant: null }],
|
||||
['Makefile', { ouvrant: '#', fermant: null }],
|
||||
]);
|
||||
|
||||
// Clé d'un fichier dans COMMENTAIRE : son nom quand il y figure, son
|
||||
// extension sinon.
|
||||
const cle = (chemin) => (COMMENTAIRE.has(basename(chemin)) ? basename(chemin) : extname(chemin));
|
||||
|
||||
// Périmètre : les fichiers de la racine et les arbres des sources, des
|
||||
// épreuves, des scripts et de la coquille. Les dépendances et les sorties de
|
||||
// construction n'y entrent pas, ni les données d'épreuve, qui sont des
|
||||
|
|
@ -55,7 +67,7 @@ function fichiersSources() {
|
|||
}
|
||||
}
|
||||
return chemins
|
||||
.filter((chemin) => COMMENTAIRE.has(extname(chemin)))
|
||||
.filter((chemin) => COMMENTAIRE.has(cle(chemin)))
|
||||
.filter((chemin) => !chemin.startsWith(DONNEES_EPREUVE))
|
||||
.sort();
|
||||
}
|
||||
|
|
@ -69,8 +81,9 @@ function fichiersSources() {
|
|||
// invalidée dans une feuille, une commande dans un script.
|
||||
function enCommentaire(premiere, seconde, { ouvrant, fermant }) {
|
||||
const suite = seconde.trim();
|
||||
if (!premiere.startsWith(ouvrant)) return false;
|
||||
if (fermant === null) return suite.startsWith(ouvrant);
|
||||
const ouvre = (ligne) => (typeof ouvrant === 'string' ? ligne.startsWith(ouvrant) : ouvrant.test(ligne));
|
||||
if (!ouvre(premiere)) return false;
|
||||
if (fermant === null) return ouvre(suite);
|
||||
const courant = !premiere.includes(fermant) && suite.endsWith(fermant);
|
||||
const repete =
|
||||
premiere.trimEnd().endsWith(fermant) &&
|
||||
|
|
@ -79,21 +92,22 @@ function enCommentaire(premiere, seconde, { ouvrant, fermant }) {
|
|||
return courant || repete;
|
||||
}
|
||||
|
||||
// Vrai quand le contenu s'ouvre sur l'en-tête, après un éventuel shebang ou
|
||||
// la déclaration <!doctype html> : deux lignes de commentaire du langage de
|
||||
// l'extension, dont le texte, débarrassé des marqueurs, est celui de
|
||||
// l'en-tête.
|
||||
function porteLEnTete(contenu, extension) {
|
||||
let lignes = contenu.split('\n');
|
||||
// Vrai quand le contenu s'ouvre sur l'en-tête, après une éventuelle marque
|
||||
// d'ordre d'octets, un shebang ou la déclaration <!doctype html> : deux
|
||||
// lignes de commentaire du langage de la clé, extension ou nom, dont le
|
||||
// texte, débarrassé des marqueurs, est celui de l'en-tête. Les fins de ligne
|
||||
// sont celles d'Unix ou de Windows.
|
||||
function porteLEnTete(contenu, cleFichier) {
|
||||
let lignes = contenu.replace(/^\uFEFF/, '').split(/\r?\n/);
|
||||
if (lignes[0].startsWith('#!')) lignes = lignes.slice(1);
|
||||
if (/^<!doctype html>$/i.test(lignes[0])) lignes = lignes.slice(1);
|
||||
const [premiere = '', seconde = ''] = lignes;
|
||||
const texte = (ligne) =>
|
||||
ligne
|
||||
.replace(/^\s*(?:\/\/|#|<!--|\/\*|\*)?\s*/, '')
|
||||
.replace(/^\s*(?:\/\/|#|<!--|\/\*|\*|@?rem(?=\s))?\s*/i, '')
|
||||
.replace(/\s*(?:-->|\*\/)?\s*$/, '');
|
||||
return (
|
||||
enCommentaire(premiere, seconde, COMMENTAIRE.get(extension)) &&
|
||||
enCommentaire(premiere, seconde, COMMENTAIRE.get(cleFichier)) &&
|
||||
TITULAIRE.test(texte(premiere)) &&
|
||||
texte(seconde) === LICENCE
|
||||
);
|
||||
|
|
@ -104,7 +118,7 @@ describe('licence', () => {
|
|||
const fichiers = fichiersSources();
|
||||
assert.ok(fichiers.length > 0, 'aucun fichier source examiné');
|
||||
const sansEnTete = fichiers
|
||||
.filter((chemin) => !porteLEnTete(readFileSync(chemin, 'utf8'), extname(chemin)))
|
||||
.filter((chemin) => !porteLEnTete(readFileSync(chemin, 'utf8'), cle(chemin)))
|
||||
.map((chemin) => relative(RACINE, chemin));
|
||||
assert.deepEqual(sansEnTete, []);
|
||||
});
|
||||
|
|
@ -129,6 +143,16 @@ describe('licence', () => {
|
|||
['.html', `<!-- ${titulaire}\n ${LICENCE}\n-->\n`, false],
|
||||
['.css', `/* ${titulaire} */\n${LICENCE}\n`, false],
|
||||
['.css', `/* ${titulaire} */\n * ${LICENCE} */\n`, false],
|
||||
['.ps1', `\uFEFF# ${titulaire}\r\n# ${LICENCE}\r\n`, true],
|
||||
['.ps1', `# ${titulaire}\n# ${LICENCE}\n`, true],
|
||||
['.nix', `# ${titulaire}\n# ${LICENCE}\n`, true],
|
||||
['.cmd', `@REM ${titulaire}\r\n@REM ${LICENCE}\r\n@echo off\r\n`, true],
|
||||
['.cmd', `REM ${titulaire}\r\nrem ${LICENCE}\r\n`, true],
|
||||
['Makefile', `# ${titulaire}\n# ${LICENCE}\n`, true],
|
||||
['.ps1', `\uFEFF# ${titulaire}\r\nWrite-Host '${LICENCE}'\r\n`, false],
|
||||
['.cmd', `@REM ${titulaire}\r\n@echo ${LICENCE}\r\n`, false],
|
||||
['.cmd', `@REMARQUE ${titulaire}\r\n@REM ${LICENCE}\r\n`, false],
|
||||
['Makefile', `# ${titulaire}\n\t${LICENCE}\n`, false],
|
||||
];
|
||||
const ecarts = cas
|
||||
.filter(([extension, contenu, admis]) => porteLEnTete(contenu, extension) !== admis)
|
||||
|
|
|
|||
Loading…
Reference in a new issue