create-openclass-uniminuto 1.4.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/template/README.md +329 -326
- package/template/package-lock.json +11576 -11568
- package/template/package.json +65 -61
- package/template/scripts/generar-desde-config.mjs +632 -279
|
@@ -1,279 +1,632 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Genera o actualiza una Open Class Slidev a partir de openclass.config.json.
|
|
3
|
-
*
|
|
4
|
-
* Uso principal:
|
|
5
|
-
* npm run config
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
function
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Genera o actualiza una Open Class Slidev a partir de openclass.config.json.
|
|
3
|
+
*
|
|
4
|
+
* Uso principal:
|
|
5
|
+
* npm run config
|
|
6
|
+
<<<<<<< HEAD
|
|
7
|
+
* npm run semana -- 6
|
|
8
|
+
* node scripts/generar-desde-config.mjs --config config/openclass.config.iot-ejemplo.json --force
|
|
9
|
+
*
|
|
10
|
+
* Mejoras:
|
|
11
|
+
* - Mantiene el curso en 8 semanas.
|
|
12
|
+
* - Repara public/favicon.png si no existe.
|
|
13
|
+
* - Elimina demo_semanaX.md cuando el curso ya tiene un nombre corto diferente de "demo".
|
|
14
|
+
* - No sobrescribe el contenido académico ya editado en semanas/*.md salvo con --force.
|
|
15
|
+
=======
|
|
16
|
+
* node scripts/generar-desde-config.mjs --config config/openclass.config.iot-ejemplo.json --force
|
|
17
|
+
>>>>>>> 55a9d4cb0e83dc9640a84aea0dc007a5379aba33
|
|
18
|
+
*/
|
|
19
|
+
import fs from "node:fs";
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
|
|
22
|
+
const args = process.argv.slice(2);
|
|
23
|
+
|
|
24
|
+
function valueAfter(flag, fallback) {
|
|
25
|
+
const i = args.indexOf(flag);
|
|
26
|
+
if (i >= 0 && args[i + 1]) return args[i + 1];
|
|
27
|
+
<<<<<<< HEAD
|
|
28
|
+
|
|
29
|
+
const pair = args.find((arg) => arg.startsWith(`${flag}=`));
|
|
30
|
+
if (pair) return pair.slice(flag.length + 1);
|
|
31
|
+
|
|
32
|
+
=======
|
|
33
|
+
const pair = args.find((arg) => arg.startsWith(`${flag}=`));
|
|
34
|
+
if (pair) return pair.slice(flag.length + 1);
|
|
35
|
+
>>>>>>> 55a9d4cb0e83dc9640a84aea0dc007a5379aba33
|
|
36
|
+
return fallback;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const configPath = valueAfter("--config", "openclass.config.json");
|
|
40
|
+
const force = args.includes("--force") || args.includes("-f");
|
|
41
|
+
const dryRun = args.includes("--dry-run");
|
|
42
|
+
<<<<<<< HEAD
|
|
43
|
+
const forceCleanDemo = args.includes("--clean-demo");
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Favicon PNG mínimo de respaldo.
|
|
47
|
+
*
|
|
48
|
+
* No reemplaza un favicon existente.
|
|
49
|
+
* Solo se usa si public/favicon.png no existe, para evitar que Slidev o GitHub Pages
|
|
50
|
+
* queden apuntando a un recurso inexistente.
|
|
51
|
+
*/
|
|
52
|
+
const FALLBACK_FAVICON_PNG_BASE64 =
|
|
53
|
+
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=";
|
|
54
|
+
=======
|
|
55
|
+
>>>>>>> 55a9d4cb0e83dc9640a84aea0dc007a5379aba33
|
|
56
|
+
|
|
57
|
+
function fail(message) {
|
|
58
|
+
console.error(`\n❌ ${message}\n`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function readJson(filePath) {
|
|
63
|
+
<<<<<<< HEAD
|
|
64
|
+
if (!fs.existsSync(filePath)) {
|
|
65
|
+
fail(`No se encontró el archivo de configuración: ${filePath}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
=======
|
|
69
|
+
if (!fs.existsSync(filePath)) fail(`No se encontró el archivo de configuración: ${filePath}`);
|
|
70
|
+
>>>>>>> 55a9d4cb0e83dc9640a84aea0dc007a5379aba33
|
|
71
|
+
try {
|
|
72
|
+
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
73
|
+
} catch (error) {
|
|
74
|
+
fail(`No se pudo leer ${filePath}. Revisa que sea JSON válido.\n${error.message}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function ensureDir(dir) {
|
|
79
|
+
if (!dryRun) fs.mkdirSync(dir, { recursive: true });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function writeFileSafe(filePath, content, { overwrite = true, label = filePath } = {}) {
|
|
83
|
+
ensureDir(path.dirname(filePath));
|
|
84
|
+
|
|
85
|
+
if (fs.existsSync(filePath) && !overwrite) {
|
|
86
|
+
console.log(`⏭ Conservado: ${label}`);
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!dryRun) fs.writeFileSync(filePath, content, "utf-8");
|
|
91
|
+
console.log(`${dryRun ? "🔎" : "✓"} ${label}`);
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
<<<<<<< HEAD
|
|
96
|
+
function writeBinaryFileIfMissing(filePath, base64Content, { label = filePath } = {}) {
|
|
97
|
+
ensureDir(path.dirname(filePath));
|
|
98
|
+
|
|
99
|
+
if (fs.existsSync(filePath)) {
|
|
100
|
+
console.log(`⏭ Conservado: ${label}`);
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!dryRun) {
|
|
105
|
+
fs.writeFileSync(filePath, Buffer.from(base64Content, "base64"));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
console.log(`${dryRun ? "🔎" : "✓"} ${label} creado como respaldo`);
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function removeFileIfExists(filePath, { label = filePath } = {}) {
|
|
113
|
+
if (!fs.existsSync(filePath)) return false;
|
|
114
|
+
|
|
115
|
+
if (!dryRun) fs.rmSync(filePath, { force: true });
|
|
116
|
+
console.log(`${dryRun ? "🔎" : "🧹"} Eliminado: ${label}`);
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function cleanShortName(value) {
|
|
121
|
+
return (
|
|
122
|
+
String(value || "")
|
|
123
|
+
.normalize("NFD")
|
|
124
|
+
.replace(/[\u0300-\u036f]/g, "")
|
|
125
|
+
.toLowerCase()
|
|
126
|
+
.replace(/[^a-z0-9]+/g, "")
|
|
127
|
+
.trim() || "curso"
|
|
128
|
+
);
|
|
129
|
+
=======
|
|
130
|
+
function cleanShortName(value) {
|
|
131
|
+
return String(value || "")
|
|
132
|
+
.normalize("NFD")
|
|
133
|
+
.replace(/[\u0300-\u036f]/g, "")
|
|
134
|
+
.toLowerCase()
|
|
135
|
+
.replace(/[^a-z0-9]+/g, "")
|
|
136
|
+
.trim();
|
|
137
|
+
>>>>>>> 55a9d4cb0e83dc9640a84aea0dc007a5379aba33
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function asArray(value) {
|
|
141
|
+
return Array.isArray(value) ? value : [];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function replaceTokens(template, tokens) {
|
|
145
|
+
return Object.entries(tokens).reduce(
|
|
146
|
+
(content, [key, value]) => content.replaceAll(`{{${key}}}`, String(value ?? "")),
|
|
147
|
+
template,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function readTemplate(filePath, fallback) {
|
|
152
|
+
return fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf-8") : fallback;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
<<<<<<< HEAD
|
|
156
|
+
=======
|
|
157
|
+
function normalizeBase(value) {
|
|
158
|
+
let base = value || "/";
|
|
159
|
+
if (!base.startsWith("/")) base = `/${base}`;
|
|
160
|
+
if (!base.endsWith("/")) base = `${base}/`;
|
|
161
|
+
return base;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
>>>>>>> 55a9d4cb0e83dc9640a84aea0dc007a5379aba33
|
|
165
|
+
const config = readJson(configPath);
|
|
166
|
+
const course = config.course || {};
|
|
167
|
+
const generation = config.generation || {};
|
|
168
|
+
|
|
169
|
+
const courseShort = cleanShortName(course.shortName || config.shortName || config.courseCode || "curso");
|
|
170
|
+
const courseName = String(course.fullName || course.name || config.courseName || "Nombre del curso").trim();
|
|
171
|
+
const courseYear = String(course.year || config.year || new Date().getFullYear()).trim();
|
|
172
|
+
const description = String(course.description || config.description || "Agrega aquí la descripción general del curso.").trim();
|
|
173
|
+
const openClassLabel = String(course.openClassLabel || "Open Class").trim();
|
|
174
|
+
const weeksTotal = Number(generation.weeksTotal || config.weeksTotal || config.totalWeeks || asArray(config.weeks).length || 8);
|
|
175
|
+
|
|
176
|
+
if (!courseShort) fail("El campo course.shortName es obligatorio.");
|
|
177
|
+
if (!courseName) fail("El campo course.fullName es obligatorio.");
|
|
178
|
+
<<<<<<< HEAD
|
|
179
|
+
if (!Number.isInteger(weeksTotal) || weeksTotal < 1) {
|
|
180
|
+
fail("generation.weeksTotal debe ser un número entero mayor o igual a 1.");
|
|
181
|
+
}
|
|
182
|
+
=======
|
|
183
|
+
if (!Number.isInteger(weeksTotal) || weeksTotal < 1) fail("generation.weeksTotal debe ser un número entero mayor o igual a 1.");
|
|
184
|
+
>>>>>>> 55a9d4cb0e83dc9640a84aea0dc007a5379aba33
|
|
185
|
+
|
|
186
|
+
const configuredWeeks = new Map(asArray(config.weeks).map((week) => [Number(week.number), week]));
|
|
187
|
+
|
|
188
|
+
function weekInfo(number) {
|
|
189
|
+
const week = configuredWeeks.get(number) || {};
|
|
190
|
+
const title = String(week.title || `Título semana ${number}`).trim();
|
|
191
|
+
<<<<<<< HEAD
|
|
192
|
+
|
|
193
|
+
=======
|
|
194
|
+
>>>>>>> 55a9d4cb0e83dc9640a84aea0dc007a5379aba33
|
|
195
|
+
return {
|
|
196
|
+
number,
|
|
197
|
+
title,
|
|
198
|
+
date: String(week.date || courseYear).trim(),
|
|
199
|
+
centralTheme: String(week.centralTheme || week.theme || title).trim(),
|
|
200
|
+
activity: String(week.activity || "Actividad o evaluación relacionada").trim(),
|
|
201
|
+
duration: String(week.duration || "90 minutos").trim(),
|
|
202
|
+
status: String(week.status || "active").trim(),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const allWeeks = Array.from({ length: weeksTotal }, (_, i) => weekInfo(i + 1));
|
|
207
|
+
<<<<<<< HEAD
|
|
208
|
+
|
|
209
|
+
=======
|
|
210
|
+
>>>>>>> 55a9d4cb0e83dc9640a84aea0dc007a5379aba33
|
|
211
|
+
const activeWeeksRaw = asArray(generation.activeWeeks || config.activeWeeks);
|
|
212
|
+
const activeWeekNumbers = activeWeeksRaw.length
|
|
213
|
+
? activeWeeksRaw.map(Number).filter((n) => Number.isInteger(n) && n >= 1 && n <= weeksTotal)
|
|
214
|
+
: allWeeks.filter((week) => week.status !== "draft" && week.status !== "inactive").map((week) => week.number);
|
|
215
|
+
|
|
216
|
+
const activeWeeks = allWeeks.filter((week) => activeWeekNumbers.includes(week.number));
|
|
217
|
+
<<<<<<< HEAD
|
|
218
|
+
|
|
219
|
+
=======
|
|
220
|
+
>>>>>>> 55a9d4cb0e83dc9640a84aea0dc007a5379aba33
|
|
221
|
+
const overwriteLaunchers = generation.overwriteLaunchers !== false || force;
|
|
222
|
+
const overwritePortal = generation.overwritePortal !== false || force;
|
|
223
|
+
const overwriteDecks = generation.overwriteDecks !== false || force;
|
|
224
|
+
const overwritePackageScripts = generation.overwritePackageScripts !== false || force;
|
|
225
|
+
const overwriteWeekContent = generation.overwriteWeekContent === true || force;
|
|
226
|
+
const exportPortal = generation.exportPortal === true;
|
|
227
|
+
|
|
228
|
+
<<<<<<< HEAD
|
|
229
|
+
/**
|
|
230
|
+
* Por defecto se eliminan los archivos demo cuando el curso ya no se llama "demo".
|
|
231
|
+
* Puede desactivarse desde openclass.config.json:
|
|
232
|
+
*
|
|
233
|
+
* "generation": {
|
|
234
|
+
* "cleanDemoFiles": false
|
|
235
|
+
* }
|
|
236
|
+
*/
|
|
237
|
+
const cleanDemoFiles = generation.cleanDemoFiles !== false;
|
|
238
|
+
|
|
239
|
+
const semanaTemplate = readTemplate(
|
|
240
|
+
"plantillas/semana.md",
|
|
241
|
+
`---
|
|
242
|
+
layout: slide-01-portada
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
::title::
|
|
246
|
+
{{COURSE_NAME}}
|
|
247
|
+
|
|
248
|
+
::week::
|
|
249
|
+
Semana {{WEEK_NUMBER}} — {{WEEK_TITLE}}
|
|
250
|
+
|
|
251
|
+
::date::
|
|
252
|
+
{{WEEK_DATE}}
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
layout: slide-12-cierre
|
|
256
|
+
---
|
|
257
|
+
`,
|
|
258
|
+
=======
|
|
259
|
+
const semanaTemplate = readTemplate(
|
|
260
|
+
"plantillas/semana.md",
|
|
261
|
+
`---\nlayout: slide-01-portada\n---\n\n::title::\n{{COURSE_NAME}}\n\n::week::\nSemana {{WEEK_NUMBER}} — {{WEEK_TITLE}}\n\n::date::\n{{WEEK_DATE}}\n\n---\nlayout: slide-12-cierre\n---\n`,
|
|
262
|
+
>>>>>>> 55a9d4cb0e83dc9640a84aea0dc007a5379aba33
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
const launcherTemplate = readTemplate(
|
|
266
|
+
"plantillas/launcher.md",
|
|
267
|
+
<<<<<<< HEAD
|
|
268
|
+
`---
|
|
269
|
+
theme: ./theme/uniminuto
|
|
270
|
+
title: {{COURSE_NAME}} — Semana {{WEEK_NUMBER}} — {{WEEK_TITLE}}
|
|
271
|
+
favicon: /favicon.png
|
|
272
|
+
codeCopy: true
|
|
273
|
+
transition: fade
|
|
274
|
+
routerMode: hash
|
|
275
|
+
drawings:
|
|
276
|
+
persist: false
|
|
277
|
+
src: ./semanas/{{COURSE_SHORT}}_semana{{WEEK_NUMBER}}.md
|
|
278
|
+
---
|
|
279
|
+
`,
|
|
280
|
+
=======
|
|
281
|
+
`---\ntheme: ./theme/uniminuto\ntitle: {{COURSE_NAME}} — Semana {{WEEK_NUMBER}} — {{WEEK_TITLE}}\nfavicon: /favicon.png\ncodeCopy: true\ntransition: fade\nrouterMode: hash\ndrawings:\n persist: false\nsrc: ./semanas/{{COURSE_SHORT}}_semana{{WEEK_NUMBER}}.md\n---\n`,
|
|
282
|
+
>>>>>>> 55a9d4cb0e83dc9640a84aea0dc007a5379aba33
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
function weekTokens(week) {
|
|
286
|
+
return {
|
|
287
|
+
COURSE_SHORT: courseShort,
|
|
288
|
+
COURSE_NAME: courseName,
|
|
289
|
+
COURSE_YEAR: courseYear,
|
|
290
|
+
OPEN_CLASS_LABEL: openClassLabel,
|
|
291
|
+
WEEK_NUMBER: week.number,
|
|
292
|
+
WEEK_TITLE: week.title,
|
|
293
|
+
WEEK_DATE: week.date,
|
|
294
|
+
WEEK_THEME: week.centralTheme,
|
|
295
|
+
WEEK_ACTIVITY: week.activity,
|
|
296
|
+
WEEK_DURATION: week.duration,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function portalWeekItem(week) {
|
|
301
|
+
const slug = `${courseShort}_semana${week.number}`;
|
|
302
|
+
<<<<<<< HEAD
|
|
303
|
+
|
|
304
|
+
return `### **Semana ${week.number}**
|
|
305
|
+
|
|
306
|
+
<a href="./semanas/${slug}/#/1" target="_self">${week.title}</a>
|
|
307
|
+
|
|
308
|
+
<a href="./descargas/${slug}.pdf" download>Descargar PDF</a> · <a href="./descargas/${slug}.pptx" download>Descargar PPTX</a>`;
|
|
309
|
+
=======
|
|
310
|
+
return `### **Semana ${week.number}**\n\n<a href="./semanas/${slug}/#/1" target="_self">${week.title}</a>\n\n<a href="./descargas/${slug}.pdf" download>Descargar PDF</a> · <a href="./descargas/${slug}.pptx" download>Descargar PPTX</a>`;
|
|
311
|
+
>>>>>>> 55a9d4cb0e83dc9640a84aea0dc007a5379aba33
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function buildPortal() {
|
|
315
|
+
const left = activeWeeks.length
|
|
316
|
+
<<<<<<< HEAD
|
|
317
|
+
? activeWeeks
|
|
318
|
+
.filter((_, index) => index < Math.ceil(activeWeeks.length / 2))
|
|
319
|
+
.map(portalWeekItem)
|
|
320
|
+
.join("\n\n")
|
|
321
|
+
: "### Sin semanas activas\n\nEjecuta `npm run semana -- 1` para generar la primera semana.";
|
|
322
|
+
|
|
323
|
+
const right =
|
|
324
|
+
activeWeeks
|
|
325
|
+
.filter((_, index) => index >= Math.ceil(activeWeeks.length / 2))
|
|
326
|
+
.map(portalWeekItem)
|
|
327
|
+
.join("\n\n") ||
|
|
328
|
+
"### Próximamente\n\nActiva más semanas con `npm run semana -- 2`, `npm run semana -- 3` y así sucesivamente.";
|
|
329
|
+
|
|
330
|
+
return `---
|
|
331
|
+
theme: ./theme/uniminuto
|
|
332
|
+
title: ${courseName} — ${openClassLabel}
|
|
333
|
+
favicon: /favicon.png
|
|
334
|
+
codeCopy: true
|
|
335
|
+
transition: fade
|
|
336
|
+
routerMode: hash
|
|
337
|
+
drawings:
|
|
338
|
+
persist: false
|
|
339
|
+
layout: slide-01-portada
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
::title::
|
|
343
|
+
${courseName}
|
|
344
|
+
|
|
345
|
+
::week::
|
|
346
|
+
${openClassLabel}
|
|
347
|
+
|
|
348
|
+
::date::
|
|
349
|
+
${courseYear}
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
layout: slide-08-titulo-texto
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
::title::
|
|
356
|
+
Descripción general del curso
|
|
357
|
+
|
|
358
|
+
::content::
|
|
359
|
+
${description}
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
layout: slide-08-titulo-texto
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
::title::
|
|
366
|
+
Ruta de aprendizaje
|
|
367
|
+
|
|
368
|
+
::content::
|
|
369
|
+
El curso se organiza en semanas. Cada semana cuenta con un lanzador raíz y un archivo interno en la carpeta \`semanas/\`.
|
|
370
|
+
|
|
371
|
+
Las presentaciones activas se controlan desde \`openclass.config.json\` mediante el campo \`generation.activeWeeks\`.
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
layout: slide-10-titulo-dos-columnas
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
::title::
|
|
378
|
+
Presentaciones disponibles
|
|
379
|
+
|
|
380
|
+
::left::
|
|
381
|
+
${left}
|
|
382
|
+
|
|
383
|
+
::right::
|
|
384
|
+
${right}
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
layout: slide-12-cierre
|
|
388
|
+
---
|
|
389
|
+
`;
|
|
390
|
+
=======
|
|
391
|
+
? activeWeeks.filter((_, index) => index < Math.ceil(activeWeeks.length / 2)).map(portalWeekItem).join("\n\n")
|
|
392
|
+
: "### Sin semanas activas\n\nEjecuta `npm run semana -- 1` para generar la primera semana.";
|
|
393
|
+
const right = activeWeeks.filter((_, index) => index >= Math.ceil(activeWeeks.length / 2)).map(portalWeekItem).join("\n\n") || "### Próximamente\n\nActiva más semanas con `npm run semana -- 2`, `npm run semana -- 3` y así sucesivamente.";
|
|
394
|
+
|
|
395
|
+
return `---\ntheme: ./theme/uniminuto\ntitle: ${courseName} — ${openClassLabel}\nfavicon: /favicon.png\ncodeCopy: true\ntransition: fade\nrouterMode: hash\ndrawings:\n persist: false\nlayout: slide-01-portada\n---\n\n::title::\n${courseName}\n\n::week::\n${openClassLabel}\n\n::date::\n${courseYear}\n\n---\nlayout: slide-08-titulo-texto\n---\n\n::title::\nDescripción general del curso\n\n::content::\n${description}\n\n---\nlayout: slide-08-titulo-texto\n---\n\n::title::\nRuta de aprendizaje\n\n::content::\nEl curso se organiza en semanas. Cada semana cuenta con un lanzador raíz y un archivo interno en la carpeta \`semanas/\`.\n\nLas presentaciones activas se controlan desde \`openclass.config.json\` mediante el campo \`generation.activeWeeks\`.\n\n---\nlayout: slide-10-titulo-dos-columnas\n---\n\n::title::\nPresentaciones disponibles\n\n::left::\n${left}\n\n::right::\n${right}\n\n---\nlayout: slide-12-cierre\n---\n`;
|
|
396
|
+
>>>>>>> 55a9d4cb0e83dc9640a84aea0dc007a5379aba33
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function buildDecks() {
|
|
400
|
+
const entries = [
|
|
401
|
+
<<<<<<< HEAD
|
|
402
|
+
`function normalizeBase(value) {
|
|
403
|
+
let base = value || "/";
|
|
404
|
+
if (!base.startsWith("/")) base = \`/\${base}\`;
|
|
405
|
+
if (!base.endsWith("/")) base = \`\${base}/\`;
|
|
406
|
+
return base;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const SITE_BASE = normalizeBase(process.env.SITE_BASE || "/");
|
|
410
|
+
|
|
411
|
+
function withBase(path = "") {
|
|
412
|
+
return \`\${SITE_BASE}\${path.replace(/^\\/+/, "")}\`;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export const decks = [
|
|
416
|
+
{
|
|
417
|
+
name: "openclass-${courseShort}",
|
|
418
|
+
entry: "slides.md",
|
|
419
|
+
out: "dist",
|
|
420
|
+
base: SITE_BASE,
|
|
421
|
+
exportable: ${exportPortal ? "true" : "false"},
|
|
422
|
+
},`,
|
|
423
|
+
=======
|
|
424
|
+
`function normalizeBase(value) {\n let base = value || "/";\n if (!base.startsWith("/")) base = \`/\${base}\`;\n if (!base.endsWith("/")) base = \`\${base}/\`;\n return base;\n}\n\nconst SITE_BASE = normalizeBase(process.env.SITE_BASE || "/");\n\nfunction withBase(path = "") {\n return \`\${SITE_BASE}\${path.replace(/^\\/+/, "")}\`;\n}\n\nexport const decks = [\n {\n name: "openclass-${courseShort}",\n entry: "slides.md",\n out: "dist",\n base: SITE_BASE,\n exportable: ${exportPortal ? "true" : "false"},\n },`,
|
|
425
|
+
>>>>>>> 55a9d4cb0e83dc9640a84aea0dc007a5379aba33
|
|
426
|
+
];
|
|
427
|
+
|
|
428
|
+
for (const week of activeWeeks) {
|
|
429
|
+
const slug = `${courseShort}_semana${week.number}`;
|
|
430
|
+
<<<<<<< HEAD
|
|
431
|
+
|
|
432
|
+
entries.push(` {
|
|
433
|
+
name: "${slug}",
|
|
434
|
+
entry: "${slug}.md",
|
|
435
|
+
out: "dist/semanas/${slug}",
|
|
436
|
+
base: withBase("semanas/${slug}/"),
|
|
437
|
+
exportable: true,
|
|
438
|
+
},`);
|
|
439
|
+
=======
|
|
440
|
+
entries.push(` {\n name: "${slug}",\n entry: "${slug}.md",\n out: "dist/semanas/${slug}",\n base: withBase("semanas/${slug}/"),\n exportable: true,\n },`);
|
|
441
|
+
>>>>>>> 55a9d4cb0e83dc9640a84aea0dc007a5379aba33
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
entries.push(`];\n`);
|
|
445
|
+
return entries.join("\n");
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function buildScripts() {
|
|
449
|
+
const scripts = {
|
|
450
|
+
dev: "slidev slides.md --open --port 3000",
|
|
451
|
+
config: "node scripts/generar-desde-config.mjs",
|
|
452
|
+
semana: "node scripts/semana.mjs",
|
|
453
|
+
"pages:check": "node scripts/preparar-github-pages.mjs",
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
for (let i = 1; i <= weeksTotal; i++) {
|
|
457
|
+
const slug = `${courseShort}_semana${i}`;
|
|
458
|
+
scripts[`dev:s${i}`] = `slidev ${slug}.md --open --port ${3000 + i}`;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
scripts.clean = "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"";
|
|
462
|
+
<<<<<<< HEAD
|
|
463
|
+
scripts["clean:downloads"] = "node -e \"require('fs').rmSync('public/descargas',{recursive:true,force:true}); require('fs').mkdirSync('public/descargas',{recursive:true})\"";
|
|
464
|
+
=======
|
|
465
|
+
scripts["clean:downloads"] = "node -e \"require('fs').rmSync('public/descargas',{recursive:true,force:true})\"";
|
|
466
|
+
>>>>>>> 55a9d4cb0e83dc9640a84aea0dc007a5379aba33
|
|
467
|
+
scripts["clean:cache"] = "node -e \"require('fs').rmSync('.slidev',{recursive:true,force:true}); require('fs').rmSync('node_modules/.vite',{recursive:true,force:true})\"";
|
|
468
|
+
scripts["build:index"] = "slidev build slides.md --out dist --base / --without-notes";
|
|
469
|
+
|
|
470
|
+
for (let i = 1; i <= weeksTotal; i++) {
|
|
471
|
+
const slug = `${courseShort}_semana${i}`;
|
|
472
|
+
scripts[`build:s${i}`] = `slidev build ${slug}.md --out dist/semanas/${slug} --base /semanas/${slug}/ --without-notes`;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
scripts["build:all"] = "node scripts/build-site.mjs";
|
|
476
|
+
scripts["build:incremental"] = "node scripts/build-incremental.mjs";
|
|
477
|
+
scripts["export:downloads"] = "node scripts/export-downloads.mjs";
|
|
478
|
+
scripts["export:incremental"] = "node scripts/export-incremental.mjs";
|
|
479
|
+
scripts.vista = "npm run clean && npm run export:downloads && npm run build:all && http-server dist -p 4173 -c-1 -o";
|
|
480
|
+
scripts.publicar = "node scripts/publicar.mjs";
|
|
481
|
+
scripts["dev:todo"] = "node scripts/dev-all.mjs";
|
|
482
|
+
scripts.nuevo = "node scripts/nuevo-curso.mjs";
|
|
483
|
+
scripts["zip:template"] = "node scripts/zip-template.mjs";
|
|
484
|
+
scripts["preview:static"] = "npm run build:all && http-server dist -p 4173 -c-1";
|
|
485
|
+
scripts["preview:pages"] = "npm run export:downloads && npm run build:all && http-server dist -p 4173 -c-1";
|
|
486
|
+
scripts["pages:build"] = "npm run export:downloads && npm run build:all";
|
|
487
|
+
scripts["pages:preview"] = "npm run preview:pages";
|
|
488
|
+
|
|
489
|
+
return scripts;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
<<<<<<< HEAD
|
|
493
|
+
function ensureProjectAssets() {
|
|
494
|
+
ensureDir("public");
|
|
495
|
+
ensureDir("public/descargas");
|
|
496
|
+
ensureDir("public/imagenes");
|
|
497
|
+
ensureDir("public/videos");
|
|
498
|
+
|
|
499
|
+
writeBinaryFileIfMissing("public/favicon.png", FALLBACK_FAVICON_PNG_BASE64, {
|
|
500
|
+
label: "public/favicon.png",
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function cleanLegacyDemoFiles() {
|
|
505
|
+
const shouldClean = forceCleanDemo || (cleanDemoFiles && courseShort !== "demo");
|
|
506
|
+
|
|
507
|
+
if (!shouldClean) {
|
|
508
|
+
console.log("⏭ Archivos demo conservados.");
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
for (let i = 1; i <= 8; i++) {
|
|
513
|
+
removeFileIfExists(`demo_semana${i}.md`, {
|
|
514
|
+
label: `demo_semana${i}.md`,
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
removeFileIfExists(path.join("semanas", `demo_semana${i}.md`), {
|
|
518
|
+
label: path.join("semanas", `demo_semana${i}.md`),
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
=======
|
|
524
|
+
>>>>>>> 55a9d4cb0e83dc9640a84aea0dc007a5379aba33
|
|
525
|
+
console.log("\n┌──────────────────────────────────────────────┐");
|
|
526
|
+
console.log("│ Generador Open Class UNIMINUTO desde config │");
|
|
527
|
+
console.log("└──────────────────────────────────────────────┘\n");
|
|
528
|
+
console.log(`Configuración : ${configPath}`);
|
|
529
|
+
console.log(`Curso : ${courseName}`);
|
|
530
|
+
console.log(`Nombre corto : ${courseShort}`);
|
|
531
|
+
console.log(`Semanas : ${weeksTotal}`);
|
|
532
|
+
console.log(`Activas : ${activeWeeks.map((w) => w.number).join(", ") || "ninguna"}\n`);
|
|
533
|
+
|
|
534
|
+
ensureDir("semanas");
|
|
535
|
+
<<<<<<< HEAD
|
|
536
|
+
ensureProjectAssets();
|
|
537
|
+
cleanLegacyDemoFiles();
|
|
538
|
+
|
|
539
|
+
writeFileSafe("slides.md", buildPortal(), {
|
|
540
|
+
overwrite: overwritePortal,
|
|
541
|
+
label: "slides.md",
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
writeFileSafe("scripts/decks.mjs", buildDecks(), {
|
|
545
|
+
overwrite: overwriteDecks,
|
|
546
|
+
label: "scripts/decks.mjs",
|
|
547
|
+
});
|
|
548
|
+
=======
|
|
549
|
+
ensureDir("public/descargas");
|
|
550
|
+
ensureDir("public/imagenes");
|
|
551
|
+
ensureDir("public/videos");
|
|
552
|
+
|
|
553
|
+
writeFileSafe("slides.md", buildPortal(), { overwrite: overwritePortal, label: "slides.md" });
|
|
554
|
+
writeFileSafe("scripts/decks.mjs", buildDecks(), { overwrite: overwriteDecks, label: "scripts/decks.mjs" });
|
|
555
|
+
>>>>>>> 55a9d4cb0e83dc9640a84aea0dc007a5379aba33
|
|
556
|
+
|
|
557
|
+
for (const week of allWeeks) {
|
|
558
|
+
const slug = `${courseShort}_semana${week.number}`;
|
|
559
|
+
const tokens = weekTokens(week);
|
|
560
|
+
<<<<<<< HEAD
|
|
561
|
+
|
|
562
|
+
=======
|
|
563
|
+
>>>>>>> 55a9d4cb0e83dc9640a84aea0dc007a5379aba33
|
|
564
|
+
writeFileSafe(`${slug}.md`, replaceTokens(launcherTemplate, tokens), {
|
|
565
|
+
overwrite: overwriteLaunchers,
|
|
566
|
+
label: `${slug}.md`,
|
|
567
|
+
});
|
|
568
|
+
<<<<<<< HEAD
|
|
569
|
+
|
|
570
|
+
=======
|
|
571
|
+
>>>>>>> 55a9d4cb0e83dc9640a84aea0dc007a5379aba33
|
|
572
|
+
writeFileSafe(path.join("semanas", `${slug}.md`), replaceTokens(semanaTemplate, tokens), {
|
|
573
|
+
overwrite: overwriteWeekContent,
|
|
574
|
+
label: path.join("semanas", `${slug}.md`),
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (overwritePackageScripts && fs.existsSync("package.json")) {
|
|
579
|
+
const pkg = JSON.parse(fs.readFileSync("package.json", "utf-8"));
|
|
580
|
+
<<<<<<< HEAD
|
|
581
|
+
|
|
582
|
+
pkg.name = `openclass-${courseShort}`;
|
|
583
|
+
pkg.description = `Presentaciones Open Class del curso ${courseName}.`;
|
|
584
|
+
pkg.scripts = buildScripts();
|
|
585
|
+
|
|
586
|
+
writeFileSafe("package.json", JSON.stringify(pkg, null, 2) + "\n", {
|
|
587
|
+
overwrite: true,
|
|
588
|
+
label: "package.json",
|
|
589
|
+
});
|
|
590
|
+
=======
|
|
591
|
+
pkg.name = `openclass-${courseShort}`;
|
|
592
|
+
pkg.description = `Presentaciones Open Class del curso ${courseName}.`;
|
|
593
|
+
pkg.scripts = buildScripts();
|
|
594
|
+
writeFileSafe("package.json", JSON.stringify(pkg, null, 2) + "\n", { overwrite: true, label: "package.json" });
|
|
595
|
+
>>>>>>> 55a9d4cb0e83dc9640a84aea0dc007a5379aba33
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (fs.existsSync("package-lock.json")) {
|
|
599
|
+
try {
|
|
600
|
+
const lock = JSON.parse(fs.readFileSync("package-lock.json", "utf-8"));
|
|
601
|
+
<<<<<<< HEAD
|
|
602
|
+
|
|
603
|
+
=======
|
|
604
|
+
>>>>>>> 55a9d4cb0e83dc9640a84aea0dc007a5379aba33
|
|
605
|
+
lock.name = `openclass-${courseShort}`;
|
|
606
|
+
if (lock.packages && lock.packages[""]) {
|
|
607
|
+
lock.packages[""].name = `openclass-${courseShort}`;
|
|
608
|
+
}
|
|
609
|
+
<<<<<<< HEAD
|
|
610
|
+
|
|
611
|
+
writeFileSafe("package-lock.json", JSON.stringify(lock, null, 2) + "\n", {
|
|
612
|
+
overwrite: true,
|
|
613
|
+
label: "package-lock.json",
|
|
614
|
+
});
|
|
615
|
+
=======
|
|
616
|
+
writeFileSafe("package-lock.json", JSON.stringify(lock, null, 2) + "\n", { overwrite: true, label: "package-lock.json" });
|
|
617
|
+
>>>>>>> 55a9d4cb0e83dc9640a84aea0dc007a5379aba33
|
|
618
|
+
} catch {
|
|
619
|
+
console.log("⚠️ package-lock.json no se actualizó porque no parece ser JSON válido.");
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
console.log("\n✅ Configuración generada.");
|
|
624
|
+
<<<<<<< HEAD
|
|
625
|
+
console.log(" Vista de una semana: npm run dev:s1");
|
|
626
|
+
console.log(" Vista completa: npm run vista");
|
|
627
|
+
console.log(" Exportar PDF/PPTX: npm run export:downloads\n");
|
|
628
|
+
=======
|
|
629
|
+
console.log(" Próximo paso sugerido: npm install");
|
|
630
|
+
console.log(" Vista de una semana: npm run dev:s1");
|
|
631
|
+
console.log(" Vista completa: npm run vista\n");
|
|
632
|
+
>>>>>>> 55a9d4cb0e83dc9640a84aea0dc007a5379aba33
|