Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 1
// This file is part of Moodle - http://moodle.org/
2
//
3
// Moodle is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, either version 3 of the License, or
6
// (at your option) any later version.
7
//
8
// Moodle is distributed in the hope that it will be useful,
9
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
// GNU General Public License for more details.
12
//
13
// You should have received a copy of the GNU General Public License
14
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
15
 
16
import yaml from 'js-yaml';
17
import path from 'path';
18
import { writeFile, mkdir, readdir, readFile, unlink } from 'fs/promises';
19
import { isValidNoteName, sortNoteTypes } from './noteTypes.mjs';
20
import { sortComponents } from './components.mjs';
21
 
22
const unreleasedPath = path.resolve('.upgradenotes');
23
 
24
/**
25
 * Get the filename for the note.
26
 *
27
 * @param {string} issueNumnber The issue number
28
 * @returns {string}
29
 */
30
const getFilename = (issueNumber) => {
31
    const dateTimeFormat = new Intl.DateTimeFormat(undefined, {
32
        year: 'numeric',
33
        month: '2-digit',
34
        day: '2-digit',
35
        hour: '2-digit',
36
        minute: '2-digit',
37
        second: '2-digit',
38
        fractionalSecondDigits: 2,
39
        timeZone: 'UTC',
40
    });
41
 
42
    const date = Object.fromEntries(
43
        dateTimeFormat.formatToParts(new Date())
44
            .filter((p) => p.type !== 'literal')
45
            .map((p) => ([p.type, p.value]))
46
    );
47
 
48
    const dateString = [
49
        date.year,
50
        date.month,
51
        date.day,
52
        date.hour,
53
        date.minute,
54
        date.second,
55
        date.fractionalSecond,
56
    ].join('');
57
 
58
    return `${issueNumber}-${dateString}.yml`;
59
};
60
 
61
/**
62
 * Create a new note.
63
 *
64
 * @param {string} issueNumber
65
 * @param {[type: string]: {message: string}} messages
66
 * @returns {string} The path to the note on disk
67
 */
68
export const createNote = async (
69
    issueNumber,
70
    messages,
71
    notePath,
72
) => {
73
    const note = {
74
        issueNumber,
75
        notes: {},
76
    };
77
 
78
    messages.forEach(({components, type, message}) => {
79
        note.notes[components] = note.notes[components] || [];
80
        note.notes[components].push({message, type});
81
    });
82
 
83
    if (!notePath) {
84
        notePath = path.resolve(unreleasedPath, getFilename(issueNumber));
85
    }
86
    const noteContent = yaml.dump(note);
87
 
88
    await mkdir(unreleasedPath, {recursive: true});
89
    await writeFile(notePath, noteContent);
90
 
91
    return notePath;
92
};
93
 
94
/**
95
 * Get all unreleased notes.
96
 *
97
 * @returns {Promise<{issueNumber: string, components: string[], types: {[type: string]: {message: string}[]}}[]>
98
 */
99
export const getAllNotes = async () => {
100
    const files = await readdir(unreleasedPath);
101
    const notes = files
102
        .filter((file) => file.endsWith('.yml'))
103
        .map(async (file) => {
104
            const filePath = path.resolve(unreleasedPath, file);
105
            const fileContent = await readFile(filePath, 'utf8');
106
 
107
            return {
108
                ...yaml.load(fileContent),
109
                filePath,
110
            };
111
        });
112
 
113
    return Promise.all(notes);
114
};
115
 
116
/**
117
 * Get the list of notes, grouped by note type, then component.
118
 *
119
 * @returns {Promise<{[type: string]: {[components: string]: {message: string, issueNumber: string}[]}}>}
120
 */
121
export const getCombinedNotes = async () => {
122
    const notes = await getAllNotes();
123
    const combinedNotes = {};
124
 
125
    notes.forEach((note) => {
126
        Object.entries(note.notes).forEach(([components, data]) => {
127
            data.forEach((entry) => {
128
                if (!isValidNoteName(entry.type)) {
129
                    throw new Error(`Invalid note type: "${entry.type}" in file ${note.filePath}`);
130
                }
131
                combinedNotes[entry.type] = combinedNotes[entry.type] || {};
132
                combinedNotes[entry.type][components] = combinedNotes[entry.type][components] || [];
133
                combinedNotes[entry.type][components].push({message: entry.message, issueNumber: note.issueNumber});
134
            });
135
        });
136
    });
137
 
138
    return combinedNotes;
139
};
140
 
141
/**
142
 * Get the list of notes, grouped by component, then by note type.
143
 *
144
 * @returns {Promise<{[component: string]: {[type: string]: {message: string, issueNumber: string}[]}}>}
145
 */
146
export const getCombinedNotesByComponent = async () => {
147
    const notes = await getAllNotes();
148
    const combinedNotes = {};
149
 
150
    notes.forEach((note) => {
151
        Object.entries(note.notes).forEach(([component, data]) => {
152
            combinedNotes[component] = combinedNotes[component] || {};
153
            data.forEach((entry) => {
154
                if (!isValidNoteName(entry.type)) {
155
                    throw new Error(`Invalid note type: "${entry.type}" in file ${note.filePath}`);
156
                }
157
                combinedNotes[component][entry.type] = combinedNotes[component][entry.type] || [];
158
                combinedNotes[component][entry.type].push({
159
                    component,
160
                    message: entry.message,
161
                    issueNumber: note.issueNumber,
162
                    type: entry.type,
163
                });
164
            });
165
        });
166
    });
167
 
168
    // Sort notes by note type.
169
    Object.entries(combinedNotes).forEach(([component, types]) => {
170
        combinedNotes[component] = Object.fromEntries(
171
            Object.entries(types).sort(([a], [b]) => sortNoteTypes(a, b))
172
        );
173
    });
174
 
175
    // Sort components.
176
    return Object.fromEntries(
177
        Object.entries(combinedNotes).sort(([a], [b]) => sortComponents(a, b))
178
    );
179
};
180
 
181
/**
182
 * Delete all unreleased notes.
183
 *
184
 * @returns {Promise<void>}
185
 */
186
export const deleteAllNotes = async () => {
187
    const files = await readdir(unreleasedPath);
188
    return Promise.all(
189
        files
190
            .filter((item, index) => files.indexOf(item) === index)
191
            .filter((file) => file.endsWith('.yml'))
192
            .map((file) => unlink(`${unreleasedPath}/${file}`))
193
    );
194
};