feat(planning): grille hebdomadaire complète avec API et filtres

- Connexion API via proxy Angular (résolution CORS, base path /api)
- Import CSS ng-zorro global pour les modales et composants
- Filtres Camion/Show câblés sur l'affichage de la grille
- Camions affichés via TrucksService (linkés au show du même créneau)
- Panneau de détails : spectacles + camions du jour sélectionné
- Modale de création de spectacle stylisée avec fond et centrage
- Positionnement précis des events à la minute dans leur créneau
- Auto-scroll vers l'heure courante au chargement
- Ligne "maintenant" sur la colonne du jour actuel
- Régénération des services OpenAPI (nouveaux noms de types)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 20:36:03 +02:00
parent 150b97cd2e
commit 654b297e2e
3131 changed files with 149304 additions and 104334 deletions
+3 -17
View File
@@ -144,10 +144,6 @@ Like `normalize` but intended for preparing package.json files for publish.
---
### `PackageJson.syncNormalize()`
This calls normalize synchronously. Most consumers of this package should avoid using this. It was added because some parts of npm were normalizing package content in class constructors and needed this affordance. It will silently ignore any asynchronous steps asked for. Again, this is a compatiblity affordance for some code in npm that is currently impossible to change without a significant semver major change, and is best not used.
### **static** `async PackageJson.prepare(path, opts = {})`
Convenience static that calls `load` before calling `prepare`
@@ -237,20 +233,10 @@ pkgJson.content
---
### `async PackageJson.save([options])`
### `async PackageJson.save()`
Saves the current `content` to the same location used when calling `load()`.
- `options`: `Object` (optional)
- `sort`: `Boolean` (optional) — If true, sorts the keys in the resulting `package.json` file for consistency and readability.
> [!NOTE]
> The sort order for `package.json` is based on the conventions from
> [sort-package-json](https://github.com/keithamus/sort-package-json/blob/main/defaultRules.md),
> cross-checked with the official npm types and documentation:
> - https://github.com/npm/types/blob/main/types/index.d.ts#L104
> - https://docs.npmjs.com/cli/configuring-npm/package-json
Saves the current `content` to the same location used when calling
`load()`.
## LICENSE
+16 -26
View File
@@ -5,7 +5,7 @@ const parseJSON = require('json-parse-even-better-errors')
const updateDeps = require('./update-dependencies.js')
const updateScripts = require('./update-scripts.js')
const updateWorkspaces = require('./update-workspaces.js')
const { normalize, syncNormalize } = require('./normalize.js')
const normalize = require('./normalize.js')
const { read, parse } = require('./read-package.js')
const { packageSort } = require('./sort.js')
@@ -25,18 +25,6 @@ const knownKeys = new Set([
])
class PackageJson {
// npm pkg fix
static fixSteps = Object.freeze([
'binRefs',
'bundleDependencies',
'fixName',
'fixVersionField',
'fixRepositoryField',
'fixDependencies',
'devDependencies',
'scriptpath',
])
static normalizeSteps = Object.freeze([
'_id',
'_attributes',
@@ -46,7 +34,20 @@ class PackageJson {
'scripts',
'funding',
'bin',
'binDir',
])
// npm pkg fix
static fixSteps = Object.freeze([
'binRefs',
'bundleDependencies',
'bundleDependenciesFalse',
'fixName',
'fixNameField',
'fixVersionField',
'fixRepositoryField',
'fixDependencies',
'devDependencies',
'scriptpath',
])
static prepareSteps = Object.freeze([
@@ -163,11 +164,7 @@ class PackageJson {
return this
}
// Manually set data from an existing object
fromContent (data) {
if (!data || typeof data !== 'object') {
throw new Error('Content data must be an object')
}
this.#manifest = data
this.#canSave = false
return this
@@ -225,7 +222,7 @@ class PackageJson {
this.#manifest = step({ content, originalContent: this.content })
}
// unknown properties will just be overwritten
// unknown properties will just be overwitten
for (const [key, value] of Object.entries(content)) {
if (!knownKeys.has(key)) {
this.content[key] = value
@@ -262,13 +259,6 @@ class PackageJson {
}
}
// steps is NOT overrideable here because this is a legacy function that's not being used in new places
syncNormalize (opts = {}) {
opts.steps = this.constructor.normalizeSteps.filter(s => s !== '_attributes')
syncNormalize(this, opts)
return this
}
async normalize (opts = {}) {
if (!opts.steps) {
opts.steps = this.constructor.normalizeSteps
+9 -6
View File
@@ -1,8 +1,8 @@
// Originally normalize-package-data
const { URL } = require('node:url')
const url = require('node:url')
const hostedGitInfo = require('hosted-git-info')
const validateLicense = require('./license.js')
const validateLicense = require('validate-npm-package-license')
const typos = {
dependancies: 'dependencies',
@@ -123,7 +123,8 @@ function normalizeData (data, changes) {
if (typeof data.bugs === 'string') {
if (isEmail(data.bugs)) {
data.bugs = { email: data.bugs }
} else if (URL.canParse(data.bugs)) {
/* eslint-disable-next-line node/no-deprecated-api */
} else if (url.parse(data.bugs).protocol) {
data.bugs = { url: data.bugs }
} else {
changes?.push(`Bug string field must be url, email, or {email,url}`)
@@ -139,7 +140,8 @@ function normalizeData (data, changes) {
const oldBugs = data.bugs
data.bugs = {}
if (oldBugs.url) {
if (URL.canParse(oldBugs.url)) {
/* eslint-disable-next-line node/no-deprecated-api */
if (typeof (oldBugs.url) === 'string' && url.parse(oldBugs.url).protocol) {
data.bugs.url = oldBugs.url
} else {
changes?.push('bugs.url field must be a string url. Deleted.')
@@ -214,7 +216,8 @@ function normalizeData (data, changes) {
changes?.push('homepage field must be a string url. Deleted.')
delete data.homepage
} else {
if (!URL.canParse(data.homepage)) {
/* eslint-disable-next-line node/no-deprecated-api */
if (!url.parse(data.homepage).protocol) {
data.homepage = 'http://' + data.homepage
}
}
@@ -230,7 +233,7 @@ function normalizeData (data, changes) {
changes?.push('No license field.')
} else if (typeof (license) !== 'string' || license.length < 1 || license.trim() === '') {
changes?.push('license should be a valid SPDX license expression')
} else if (!validateLicense(license)) {
} else if (!validateLicense(license).validForNewPackages) {
changes?.push('license should be a valid SPDX license expression')
}
// fixPeople
+211 -224
View File
@@ -67,7 +67,7 @@ function normalizePackageBin (pkg, changes) {
changes?.push(`"bin[${binKey}]" was renamed to "bin[${base}]"`)
}
if (binTarget !== pkg.bin[binKey]) {
changes?.push(`"bin[${base}]" script name ${binTarget} was invalid and removed`)
changes?.push(`"bin[${base}]" script name was cleaned`)
}
pkg.bin[base] = binTarget
}
@@ -133,9 +133,15 @@ function secureAndUnixifyPath (ref) {
return secured.startsWith('./') ? '' : secured
}
// Only steps that can be ran synchronously. There are some object constructors (i.e. Aborist Node) that need synchronous normalization so here we are.
function syncSteps (pkg, { strict, steps, changes, allowLegacyCase }) {
// We don't want the `changes` array in here by default because this is a hot
// path for parsing packuments during install. So the calling method passes it
// in if it wants to track changes.
const normalize = async (pkg, { strict, steps, root, changes, allowLegacyCase }) => {
if (!pkg.content) {
throw new Error('Can not normalize without content')
}
const data = pkg.content
const scripts = data.scripts || {}
const pkgId = `${data.name ?? ''}@${data.version ?? ''}`
// name and version are load bearing so we have to clean them up first
@@ -189,7 +195,6 @@ function syncSteps (pkg, { strict, steps, changes, allowLegacyCase }) {
}
}
}
// remove attributes that start with "_"
if (steps.includes('_attributes')) {
for (const key in data) {
@@ -209,14 +214,14 @@ function syncSteps (pkg, { strict, steps, changes, allowLegacyCase }) {
}
// fix bundledDependencies typo
// normalize bundleDependencies
if (steps.includes('bundledDependencies')) {
if (data.bundleDependencies === undefined && data.bundledDependencies !== undefined) {
data.bundleDependencies = data.bundledDependencies
changes?.push(`Deleted incorrect "bundledDependencies"`)
}
changes?.push(`Deleted incorrect "bundledDependencies"`)
delete data.bundledDependencies
}
// expand "bundleDependencies: true or translate from object"
if (steps.includes('bundleDependencies')) {
const bd = data.bundleDependencies
@@ -255,6 +260,32 @@ function syncSteps (pkg, { strict, steps, changes, allowLegacyCase }) {
}
}
// add "install" attribute if any "*.gyp" files exist
if (steps.includes('gypfile')) {
if (!scripts.install && !scripts.preinstall && data.gypfile !== false) {
const files = await lazyLoadGlob()('*.gyp', { cwd: pkg.path })
if (files.length) {
scripts.install = 'node-gyp rebuild'
data.scripts = scripts
data.gypfile = true
changes?.push(`"scripts.install" was set to "node-gyp rebuild"`)
changes?.push(`"gypfile" was set to "true"`)
}
}
}
// add "start" attribute if "server.js" exists
if (steps.includes('serverjs') && !scripts.start) {
try {
await fs.access(path.join(pkg.path, 'server.js'))
scripts.start = 'node server.js'
data.scripts = scripts
changes?.push('"scripts.start" was set to "node server.js"')
} catch {
// do nothing
}
}
// strip "node_modules/.bin" from scripts entries
// remove invalid scripts entries (non-strings)
if ((steps.includes('scripts') || steps.includes('scriptpath')) && data.scripts !== undefined) {
@@ -282,6 +313,179 @@ function syncSteps (pkg, { strict, steps, changes, allowLegacyCase }) {
}
}
// populate "authors" attribute
if (steps.includes('authors') && !data.contributors) {
try {
const authorData = await fs.readFile(path.join(pkg.path, 'AUTHORS'), 'utf8')
const authors = authorData.split(/\r?\n/g)
.map(line => line.replace(/^\s*#.*$/, '').trim())
.filter(line => line)
data.contributors = authors
changes?.push('"contributors" was auto-populated with the contents of the "AUTHORS" file')
} catch {
// do nothing
}
}
// populate "readme" attribute
if (steps.includes('readme') && !data.readme) {
const mdre = /\.m?a?r?k?d?o?w?n?$/i
const files = await lazyLoadGlob()('{README,README.*}', {
cwd: pkg.path,
nocase: true,
mark: true,
})
let readmeFile
for (const file of files) {
// don't accept directories.
if (!file.endsWith(path.sep)) {
if (file.match(mdre)) {
readmeFile = file
break
}
if (file.endsWith('README')) {
readmeFile = file
}
}
}
if (readmeFile) {
const readmeData = await fs.readFile(path.join(pkg.path, readmeFile), 'utf8')
data.readme = readmeData
data.readmeFilename = readmeFile
changes?.push(`"readme" was set to the contents of ${readmeFile}`)
changes?.push(`"readmeFilename" was set to ${readmeFile}`)
}
if (!data.readme) {
data.readme = 'ERROR: No README data found!'
}
}
// expand directories.man
if (steps.includes('mans')) {
if (data.directories?.man && !data.man) {
const manDir = secureAndUnixifyPath(data.directories.man)
const cwd = path.resolve(pkg.path, manDir)
const files = await lazyLoadGlob()('**/*.[0-9]', { cwd })
data.man = files.map(man =>
path.relative(pkg.path, path.join(cwd, man)).split(path.sep).join('/')
)
}
normalizePackageMan(data, changes)
}
if (steps.includes('bin') || steps.includes('binDir') || steps.includes('binRefs')) {
normalizePackageBin(data, changes)
}
// expand "directories.bin"
if (steps.includes('binDir') && data.directories?.bin && !data.bin) {
const binsDir = path.resolve(pkg.path, secureAndUnixifyPath(data.directories.bin))
const bins = await lazyLoadGlob()('**', { cwd: binsDir })
data.bin = bins.reduce((acc, binFile) => {
if (binFile && !binFile.startsWith('.')) {
const binName = path.basename(binFile)
acc[binName] = path.join(data.directories.bin, binFile)
}
return acc
}, {})
// *sigh*
normalizePackageBin(data, changes)
}
// populate "gitHead" attribute
if (steps.includes('gitHead') && !data.gitHead) {
const git = require('@npmcli/git')
const gitRoot = await git.find({ cwd: pkg.path, root })
let head
if (gitRoot) {
try {
head = await fs.readFile(path.resolve(gitRoot, '.git/HEAD'), 'utf8')
} catch (err) {
// do nothing
}
}
let headData
if (head) {
if (head.startsWith('ref: ')) {
const headRef = head.replace(/^ref: /, '').trim()
const headFile = path.resolve(gitRoot, '.git', headRef)
try {
headData = await fs.readFile(headFile, 'utf8')
headData = headData.replace(/^ref: /, '').trim()
} catch (err) {
// do nothing
}
if (!headData) {
const packFile = path.resolve(gitRoot, '.git/packed-refs')
try {
let refs = await fs.readFile(packFile, 'utf8')
if (refs) {
refs = refs.split('\n')
for (let i = 0; i < refs.length; i++) {
const match = refs[i].match(/^([0-9a-f]{40}) (.+)$/)
if (match && match[2].trim() === headRef) {
headData = match[1]
break
}
}
}
} catch {
// do nothing
}
}
} else {
headData = head.trim()
}
}
if (headData) {
data.gitHead = headData
}
}
// populate "types" attribute
if (steps.includes('fillTypes')) {
const index = data.main || 'index.js'
if (typeof index !== 'string') {
throw new TypeError('The "main" attribute must be of type string.')
}
// TODO exports is much more complicated than this in verbose format
// We need to support for instance
// "exports": {
// ".": [
// {
// "default": "./lib/npm.js"
// },
// "./lib/npm.js"
// ],
// "./package.json": "./package.json"
// },
// as well as conditional exports
// if (data.exports && typeof data.exports === 'string') {
// index = data.exports
// }
// if (data.exports && data.exports['.']) {
// index = data.exports['.']
// if (typeof index !== 'string') {
// }
// }
const extless = path.join(path.dirname(index), path.basename(index, path.extname(index)))
const dts = `./${extless}.d.ts`
const hasDTSFields = 'types' in data || 'typings' in data
if (!hasDTSFields) {
try {
await fs.access(path.join(pkg.path, dts))
data.types = dts.split(path.sep).join('/')
} catch {
// do nothing
}
}
}
// "normalizeData" from "read-package-json", which was just a call through to
// "normalize-package-data". We only call the "fixer" functions because
// outside of that it was also clobbering _id (which we already conditionally
@@ -379,209 +583,6 @@ function syncSteps (pkg, { strict, steps, changes, allowLegacyCase }) {
const { normalizeData } = require('./normalize-data.js')
normalizeData(data, changes)
}
}
// Steps that require await, distinct from sync-steps.js
async function asyncSteps (pkg, { steps, root, changes }) {
const data = pkg.content
const scripts = data.scripts || {}
const pkgId = `${data.name ?? ''}@${data.version ?? ''}`
// add "install" attribute if any "*.gyp" files exist
if (steps.includes('gypfile')) {
if (!scripts.install && !scripts.preinstall && data.gypfile !== false) {
const files = await lazyLoadGlob()('*.gyp', { cwd: pkg.path })
if (files.length) {
scripts.install = 'node-gyp rebuild'
data.scripts = scripts
data.gypfile = true
changes?.push(`"scripts.install" was set to "node-gyp rebuild"`)
changes?.push(`"gypfile" was set to "true"`)
}
}
}
// add "start" attribute if "server.js" exists
if (steps.includes('serverjs') && !scripts.start) {
try {
await fs.access(path.join(pkg.path, 'server.js'))
scripts.start = 'node server.js'
data.scripts = scripts
changes?.push('"scripts.start" was set to "node server.js"')
} catch {
// do nothing
}
}
// populate "authors" attribute
if (steps.includes('authors') && !data.contributors) {
try {
const authorData = await fs.readFile(path.join(pkg.path, 'AUTHORS'), 'utf8')
const authors = authorData.split(/\r?\n/g)
.map(line => line.replace(/^\s*#.*$/, '').trim())
.filter(line => line)
data.contributors = authors
changes?.push('"contributors" was auto-populated with the contents of the "AUTHORS" file')
} catch {
// do nothing
}
}
// populate "readme" attribute
if (steps.includes('readme') && !data.readme) {
const mdre = /\.m?a?r?k?d?o?w?n?$/i
const files = await lazyLoadGlob()('{README,README.*}', {
cwd: pkg.path,
nocase: true,
mark: true,
})
let readmeFile
for (const file of files) {
// don't accept directories.
if (!file.endsWith(path.sep)) {
if (file.match(mdre)) {
readmeFile = file
break
}
if (file.endsWith('README')) {
readmeFile = file
}
}
}
if (readmeFile) {
const readmeData = await fs.readFile(path.join(pkg.path, readmeFile), 'utf8')
data.readme = readmeData
data.readmeFilename = readmeFile
changes?.push(`"readme" was set to the contents of ${readmeFile}`)
changes?.push(`"readmeFilename" was set to ${readmeFile}`)
}
if (!data.readme) {
data.readme = 'ERROR: No README data found!'
}
}
// expand directories.man
if (steps.includes('mans')) {
if (data.directories?.man && !data.man) {
const manDir = secureAndUnixifyPath(data.directories.man)
const cwd = path.resolve(pkg.path, manDir)
const files = await lazyLoadGlob()('**/*.[0-9]', { cwd })
data.man = files.map(man =>
path.relative(pkg.path, path.join(cwd, man)).split(path.sep).join('/')
)
}
normalizePackageMan(data, changes)
}
// expand "directories.bin"
if (steps.includes('binDir') && data.directories?.bin && !data.bin && pkg.path) {
const binPath = secureAndUnixifyPath(data.directories.bin)
const bins = await lazyLoadGlob()('**', { cwd: path.resolve(pkg.path, binPath) })
data.bin = bins.reduce((acc, binFile) => {
if (binFile && !binFile.startsWith('.')) {
const binName = path.basename(binFile)
// binPath is already cleaned and unixified, no need to path.join here.
acc[binName] = `${binPath}/${secureAndUnixifyPath(binFile)}`
}
return acc
}, {})
} else if (steps.includes('bin') || steps.includes('binDir') || steps.includes('binRefs')) {
normalizePackageBin(data, changes)
}
// populate "gitHead" attribute
if (steps.includes('gitHead') && !data.gitHead) {
const git = require('@npmcli/git')
const gitRoot = await git.find({ cwd: pkg.path, root })
let head
if (gitRoot) {
try {
head = await fs.readFile(path.resolve(gitRoot, '.git/HEAD'), 'utf8')
} catch (err) {
// do nothing
}
}
let headData
if (head) {
if (head.startsWith('ref: ')) {
const headRef = head.replace(/^ref: /, '').trim()
const headFile = path.resolve(gitRoot, '.git', headRef)
try {
headData = await fs.readFile(headFile, 'utf8')
headData = headData.replace(/^ref: /, '').trim()
} catch (err) {
// do nothing
}
if (!headData) {
const packFile = path.resolve(gitRoot, '.git/packed-refs')
try {
let refs = await fs.readFile(packFile, 'utf8')
if (refs) {
refs = refs.split('\n')
for (let i = 0; i < refs.length; i++) {
const match = refs[i].match(/^([0-9a-f]{40}) (.+)$/)
if (match && match[2].trim() === headRef) {
headData = match[1]
break
}
}
}
} catch {
// do nothing
}
}
} else {
headData = head.trim()
}
}
if (headData) {
data.gitHead = headData
}
}
// populate "types" attribute
if (steps.includes('fillTypes')) {
const index = data.main || 'index.js'
if (typeof index !== 'string') {
throw new TypeError('The "main" attribute must be of type string.')
}
// TODO exports is much more complicated than this in verbose format
// We need to support for instance
// "exports": {
// ".": [
// {
// "default": "./lib/npm.js"
// },
// "./lib/npm.js"
// ],
// "./package.json": "./package.json"
// },
// as well as conditional exports
// if (data.exports && typeof data.exports === 'string') {
// index = data.exports
// }
// if (data.exports && data.exports['.']) {
// index = data.exports['.']
// if (typeof index !== 'string') {
// }
// }
const extless = path.join(path.dirname(index), path.basename(index, path.extname(index)))
const dts = `./${extless}.d.ts`
const hasDTSFields = 'types' in data || 'typings' in data
if (!hasDTSFields) {
try {
await fs.access(path.join(pkg.path, dts))
data.types = dts.split(path.sep).join('/')
} catch {
// do nothing
}
}
}
// Warn if the bin references don't point to anything. This might be better
// in normalize-package-data if it had access to the file path.
@@ -597,18 +598,4 @@ async function asyncSteps (pkg, { steps, root, changes }) {
}
}
// We don't want the `changes` array in here by default because this is a hot path for parsing packuments during install. The calling method passes it in if it wants to track changes.
async function normalize (pkg, opts) {
if (!pkg.content) {
throw new Error('Can not normalize without content')
}
await asyncSteps(pkg, opts)
// the normalizeData part of this needs to be the last thing ran, so sync comes second
syncSteps(pkg, opts)
}
function syncNormalize (pkg, opts) {
syncSteps(pkg, opts)
}
module.exports = { normalize, syncNormalize }
module.exports = normalize
+13 -11
View File
@@ -1,6 +1,6 @@
{
"name": "@npmcli/package-json",
"version": "7.0.5",
"version": "6.2.0",
"description": "Programmatic API to update package.json",
"keywords": [
"npm",
@@ -29,25 +29,27 @@
"eslint": "eslint \"**/*.{js,cjs,ts,mjs,jsx,tsx}\""
},
"dependencies": {
"@npmcli/git": "^7.0.0",
"glob": "^13.0.0",
"hosted-git-info": "^9.0.0",
"json-parse-even-better-errors": "^5.0.0",
"proc-log": "^6.0.0",
"@npmcli/git": "^6.0.0",
"glob": "^10.2.2",
"hosted-git-info": "^8.0.0",
"json-parse-even-better-errors": "^4.0.0",
"proc-log": "^5.0.0",
"semver": "^7.5.3",
"spdx-expression-parse": "^4.0.0"
"validate-npm-package-license": "^3.0.4"
},
"devDependencies": {
"@npmcli/eslint-config": "^6.0.0",
"@npmcli/template-oss": "4.28.1",
"@npmcli/eslint-config": "^5.1.0",
"@npmcli/template-oss": "4.23.6",
"read-package-json": "^7.0.0",
"read-package-json-fast": "^4.0.0",
"tap": "^16.0.1"
},
"engines": {
"node": "^20.17.0 || >=22.9.0"
"node": "^18.17.0 || >=20.5.0"
},
"templateOSS": {
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
"version": "4.28.1",
"version": "4.23.6",
"publish": "true"
},
"tap": {