@gouvfr/dsfr-nexus
Version:
Le module `dsfr-nexus` est l'interface de ligne de commande (CLI) centrale du Système de Design de l’État - DSFR. Il offre des outils pour gérer et compiler les ressources du DSFR
313 lines (263 loc) • 8.1 kB
JavaScript
import fs from 'fs';
import { VersionState } from './version/version-state.js';
import { I18nState } from './i18n/i18n-state.js';
import { MapState } from './map/map-state.js';
import yaml from 'yaml';
import { normalize } from '@gouvfr/dsfr-forge';
import { CONFIG_FOLDER } from '../constants.js';
import { fragments } from '@gouvfr/dsfr-lore';
class State {
constructor () {
this._src = null;
this._root = null;
this._dest = null;
this._path = '/';
this._config = CONFIG_FOLDER;
this._part = null;
this._i18n = null;
this._version = null;
this._map = null;
this._partIds = [];
this._initial = null;
this._siblings = [];
}
async load () {
const dataFile = fs.readFileSync(`${this._config}/state.yml`, 'utf8');
const data = yaml.parse(dataFile);
this._root = data.root;
this._i18n = new I18nState();
this._i18n.fill(data.i18n);
this._version = new VersionState();
this._version.fill(data.version);
this._map = new MapState(data.map);
this._partIds = data.partIds;
Object.freeze(this._partIds);
this._apply();
this._initial = this;
}
_apply () {
this._banner = `/*! DSFR ${this._version.tag} | SPDX-License-Identifier: MIT | License-Filename: LICENSE.md | restricted use (see terms and conditions) */`;
}
get src () {
return this._src;
}
get root () {
return this._root;
}
get dest () {
return this._dest;
}
get path () {
return this._path;
}
get part () {
return this._part;
}
get config () {
const vertical = this._part ? `/${this._part}` : '';
return `${this._config}${vertical}`;
}
get deploy () {
return {
asset: `/${this.version.text}/asset`,
static: '/static',
lib: '/lib',
dist: '/dist',
}
}
get i18n () {
return this._i18n;
}
get version () {
return this._version;
}
get map () {
return this._map;
}
get partIds () {
return this._partIds;
}
get banner () {
return this._banner;
}
_clone () {
const state = new this.constructor();
state._src = this._src;
state._root = this._root;
state._dest = this._dest;
state._path = this._path;
state._config = this._config;
state._part = this._part;
state._i18n = this._i18n;
state._version = this._version;
state._map = this._map;
state._partIds = this._partIds;
state._banner = this._banner;
state._initial = this._initial;
state._siblings = this._siblings;
return state;
}
clone () {
const state = this._clone();
Object.freeze(state);
return state;
}
descend (src, dest = null) {
const state = this._clone();
state._src = `${this._src}/${src}`;
if (dest) state._dest = `${this._dest}/${dest}`;
Object.freeze(state);
return state;
}
localize (locale = null, src = null) {
const state = this._clone();
if (locale) state._i18n = this._i18n.localize(locale);
if (src) state._src = `${this._src}/${src}`;
Object.freeze(state);
return state;
}
setSrc (src) {
const state = this._clone();
state._src = src;
Object.freeze(state);
return state;
}
setDest (dest) {
const state = this._clone();
state._dest = dest;
Object.freeze(state);
return state;
}
setPath (path) {
const state = this._clone();
state._path = path;
Object.freeze(state);
return state;
}
setPart (part) {
const state = this._clone();
state._part = part;
Object.freeze(state);
return state;
}
setData (data) {
const state = this._clone();
if (data.src) state._src = data.src;
if (data.dest) state._dest = data.dest;
if (data.path) state._path = data.path;
if (data.part) state._part = data.part;
Object.freeze(state);
return state;
}
srcFile (filename) {
return `${this._src}/${filename}`;
}
destFile (filename) {
return `${this._dest}/${filename}`;
}
configFile (filename) {
return `${this.config}/${filename}`;
}
get urlset () {
return this._map.getUrlset(this._i18n.default.code, this._i18n.locales.map(locale => locale.code));
}
get urlMap () {
return this._map.getUrlMap(this._i18n.current.code, this._i18n.default.code, this._i18n.locales.map(locale => locale.code), this._version.text);
}
resolveItems (path, rank = null, sort = null) {
return this._map.getChildNodes(path, this._i18n.current.code, this._i18n.default.code, this._i18n.locales.map(locale => locale.code), this._version.text, rank, sort, this._src, this._path);
}
resolveItem (item) {
if (item.path) {
return {
...item,
...this._map.getNode(item.path, this._i18n.current.code, this._i18n.default.code, this._version.text, this._src, this._path)
}
}
if (item.url) {
return {
...item,
url: this.resolveFrom(item.url),
blankTitle: fragments.getFragment(this._i18n.current.code, 'blank'),
};
}
return item;
}
resolve (url) {
return this.resolveFrom(url, this._path);
}
resolveFrom (url, from = '/') {
if (/^(https:|http:|www\.)\S*$/.test(url)) return url;
if (/^#.*/.test(url)) return `#${normalize(url)}`;
if (/^path:\S*$/.test(url)) {
const path = url.replace(/^path:/, '');
const [pathWithoutAnchor, rawAnchor] = path.split('#', 2);
const anchor = rawAnchor ? `#${rawAnchor}` : '';
const node = this._map.getNode(pathWithoutAnchor, this._i18n.current.code, this._i18n.default.code, this._version.text, this._src);
if (node) return node.url + anchor;
}
const regex = /(.*)(\/)?index(@[a-z]{2})?\.md(#)?(.*)?$/.exec(url);
if (!regex) return url;
const path = regex[1];
if (typeof path !== 'string') return url;
const relative = this._map.getRelativeNode(from, path, this._i18n.current.code, this._i18n.default.code, this.version.text, this._src)?.url;
const anchor = regex[4] ? `#${normalize(regex[5])}` : '';
return `${relative}${anchor}`;
}
getUrl (path, locale = null) {
return this._map.getNode(path, locale?.code ?? this._i18n.current.code, this._i18n.default.code, this._version.text, this._src)?.url;
}
getFallbackUrl (path, locale = null) {
const url = this.getUrl(path, locale);
if (url) return url;
const fallback = path.split('/').slice(0, -1).join('/');
return this.getFallbackUrl(fallback, locale);
}
getVersionData (path, locale, active = false) {
const data = {
text: this._version.text,
major: this._version.major,
minor: this._version.minor,
url: this.getFallbackUrl(path, locale),
active: active
};
if (this._version.isCurrent) {
data.badge = fragments.getFragment(locale.code, 'current.text');
}
return data;
}
getVersionsData (path) {
const data = [ this.getVersionData(path, this._i18n.current, true) ];
for (const sibling of this._siblings) {
data.push(sibling.getVersionData(path, this._i18n.current));
}
data.sort((a, b) => {
return (b.major - a.major) * 1000 + (b.minor - a.minor);
})
return data;
}
}
const getState = async (options = {}, StateConstructor = State) => {
const state = new StateConstructor();
state._src = options.src ?? '.';
state._root = options.root ?? '.';
state._dest = options.dest ?? '.';
state._config = options.config ?? CONFIG_FOLDER;
await state.load();
return state;
};
const getStates = async (options = {}, StateConstructor = State) => {
const dirs = fs.readdirSync(CONFIG_FOLDER, { withFileTypes: true })
.filter(dir => dir.isDirectory() && fs.existsSync(`${CONFIG_FOLDER}/${dir.name}/state.yml`))
.map(dir => dir.name);
const states = [];
for (const dir of dirs) {
const state = await getState({ ...options, config: `${CONFIG_FOLDER}/${dir}` }, StateConstructor);
states.push(state);
}
for (const state of states) {
state._siblings = states.filter(sibling => sibling !== state);
}
return states;
}
export { State, getState, getStates };