create-openclass-uniminuto 1.4.1 → 1.6.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/bin/create-openclass-uniminuto.mjs +81 -1
- package/package.json +3 -3
- package/template/README.md +326 -329
- package/template/package-lock.json +11568 -11576
- package/template/package.json +63 -65
- package/template/public/imagenes/avion.png +0 -0
- package/template/public/imagenes/favicon.png +0 -0
- package/template/scripts/generar-desde-config.mjs +348 -632
- package/template/theme/uniminuto/components/AutoFitText.vue +7 -2
- package/template/theme/uniminuto/components/TitleRibbon.vue +222 -0
- package/template/theme/uniminuto/layouts/README.md +99 -0
- package/template/theme/uniminuto/layouts/slide-02-titulo.vue +29 -47
- package/template/theme/uniminuto/layouts/slide-03-imagen-izquierda.vue +43 -69
- package/template/theme/uniminuto/layouts/slide-04-imagen-derecha.vue +49 -75
- package/template/theme/uniminuto/layouts/slide-05-titulo-superior-texto-derecha.vue +34 -66
- package/template/theme/uniminuto/layouts/slide-06-titulo-superior-texto-izquierda.vue +28 -78
- package/template/theme/uniminuto/layouts/slide-07-multimedia-con-titulo.vue +10 -66
- package/template/theme/uniminuto/layouts/slide-08-titulo-texto.vue +24 -53
- package/template/theme/uniminuto/layouts/slide-09-objetivos.vue +26 -53
- package/template/theme/uniminuto/layouts/slide-10-titulo-dos-columnas.vue +27 -59
- package/template/theme/uniminuto/layouts/slide-11-dos-titulos-dos-columnas.vue +32 -65
- package/template/theme/uniminuto/layouts/slide-codigo.vue +16 -113
- package/template/theme/uniminuto/styles/base.css +214 -109
- package/template/public/fondos/slide-02-titulo.png +0 -0
- package/template/public/fondos/slide-03-imagen-izquierda.png +0 -0
- package/template/public/fondos/slide-04-imagen-derecha.png +0 -0
|
@@ -1,632 +1,348 @@
|
|
|
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
|
-
if (!dryRun) fs.
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const
|
|
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
|
-
const
|
|
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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
);
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
|
1
|
+
/**
|
|
2
|
+
* Genera o actualiza una Open Class Slidev a partir de openclass.config.json.
|
|
3
|
+
*
|
|
4
|
+
* Uso principal:
|
|
5
|
+
* npm run config
|
|
6
|
+
* node scripts/generar-desde-config.mjs --config config/openclass.config.iot-ejemplo.json --force
|
|
7
|
+
*/
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
|
|
11
|
+
const args = process.argv.slice(2);
|
|
12
|
+
|
|
13
|
+
function valueAfter(flag, fallback) {
|
|
14
|
+
const i = args.indexOf(flag);
|
|
15
|
+
if (i >= 0 && args[i + 1]) return args[i + 1];
|
|
16
|
+
const pair = args.find((arg) => arg.startsWith(`${flag}=`));
|
|
17
|
+
if (pair) return pair.slice(flag.length + 1);
|
|
18
|
+
return fallback;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const configPath = valueAfter("--config", "openclass.config.json");
|
|
22
|
+
const force = args.includes("--force") || args.includes("-f");
|
|
23
|
+
const dryRun = args.includes("--dry-run");
|
|
24
|
+
const forceCleanDemo = args.includes("--clean-demo");
|
|
25
|
+
const FALLBACK_FAVICON_PNG_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=";
|
|
26
|
+
|
|
27
|
+
function fail(message) {
|
|
28
|
+
console.error(`\n❌ ${message}\n`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function readJson(filePath) {
|
|
33
|
+
if (!fs.existsSync(filePath)) fail(`No se encontró el archivo de configuración: ${filePath}`);
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
36
|
+
} catch (error) {
|
|
37
|
+
fail(`No se pudo leer ${filePath}. Revisa que sea JSON válido.\n${error.message}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function ensureDir(dir) {
|
|
42
|
+
if (!dryRun) fs.mkdirSync(dir, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function writeFileSafe(filePath, content, { overwrite = true, label = filePath } = {}) {
|
|
46
|
+
ensureDir(path.dirname(filePath));
|
|
47
|
+
|
|
48
|
+
if (fs.existsSync(filePath) && !overwrite) {
|
|
49
|
+
console.log(`⏭ Conservado: ${label}`);
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!dryRun) fs.writeFileSync(filePath, content, "utf-8");
|
|
54
|
+
console.log(`${dryRun ? "🔎" : "✓"} ${label}`);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function writeBinaryFileIfMissing(filePath, bufferOrBase64, { label = filePath } = {}) {
|
|
59
|
+
ensureDir(path.dirname(filePath));
|
|
60
|
+
|
|
61
|
+
if (fs.existsSync(filePath)) {
|
|
62
|
+
console.log(`⏭ Conservado: ${label}`);
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!dryRun) {
|
|
67
|
+
const buffer = Buffer.isBuffer(bufferOrBase64)
|
|
68
|
+
? bufferOrBase64
|
|
69
|
+
: Buffer.from(bufferOrBase64, "base64");
|
|
70
|
+
fs.writeFileSync(filePath, buffer);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log(`${dryRun ? "🔎" : "✓"} ${label} creado`);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function removeFileIfExists(filePath, { label = filePath } = {}) {
|
|
78
|
+
if (!fs.existsSync(filePath)) return false;
|
|
79
|
+
if (!dryRun) fs.rmSync(filePath, { force: true });
|
|
80
|
+
console.log(`${dryRun ? "🔎" : "🧹"} Eliminado: ${label}`);
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function cleanShortName(value) {
|
|
85
|
+
return String(value || "")
|
|
86
|
+
.normalize("NFD")
|
|
87
|
+
.replace(/[\u0300-\u036f]/g, "")
|
|
88
|
+
.toLowerCase()
|
|
89
|
+
.replace(/[^a-z0-9]+/g, "")
|
|
90
|
+
.trim();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function asArray(value) {
|
|
94
|
+
return Array.isArray(value) ? value : [];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function replaceTokens(template, tokens) {
|
|
98
|
+
return Object.entries(tokens).reduce(
|
|
99
|
+
(content, [key, value]) => content.replaceAll(`{{${key}}}`, String(value ?? "")),
|
|
100
|
+
template,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function readTemplate(filePath, fallback) {
|
|
105
|
+
return fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf-8") : fallback;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function normalizeBase(value) {
|
|
109
|
+
let base = value || "/";
|
|
110
|
+
if (!base.startsWith("/")) base = `/${base}`;
|
|
111
|
+
if (!base.endsWith("/")) base = `${base}/`;
|
|
112
|
+
return base;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const config = readJson(configPath);
|
|
116
|
+
const course = config.course || {};
|
|
117
|
+
const generation = config.generation || {};
|
|
118
|
+
|
|
119
|
+
const courseShort = cleanShortName(course.shortName || config.shortName || config.courseCode || "curso");
|
|
120
|
+
const courseName = String(course.fullName || course.name || config.courseName || "Nombre del curso").trim();
|
|
121
|
+
const courseYear = String(course.year || config.year || new Date().getFullYear()).trim();
|
|
122
|
+
const description = String(course.description || config.description || "Agrega aquí la descripción general del curso.").trim();
|
|
123
|
+
const openClassLabel = String(course.openClassLabel || "Open Class").trim();
|
|
124
|
+
const weeksTotal = Number(generation.weeksTotal || config.weeksTotal || config.totalWeeks || asArray(config.weeks).length || 8);
|
|
125
|
+
|
|
126
|
+
if (!courseShort) fail("El campo course.shortName es obligatorio.");
|
|
127
|
+
if (!courseName) fail("El campo course.fullName es obligatorio.");
|
|
128
|
+
if (!Number.isInteger(weeksTotal) || weeksTotal < 1) fail("generation.weeksTotal debe ser un número entero mayor o igual a 1.");
|
|
129
|
+
|
|
130
|
+
const configuredWeeks = new Map(asArray(config.weeks).map((week) => [Number(week.number), week]));
|
|
131
|
+
|
|
132
|
+
function weekInfo(number) {
|
|
133
|
+
const week = configuredWeeks.get(number) || {};
|
|
134
|
+
const title = String(week.title || `Título semana ${number}`).trim();
|
|
135
|
+
return {
|
|
136
|
+
number,
|
|
137
|
+
title,
|
|
138
|
+
date: String(week.date || courseYear).trim(),
|
|
139
|
+
centralTheme: String(week.centralTheme || week.theme || title).trim(),
|
|
140
|
+
activity: String(week.activity || "Actividad o evaluación relacionada").trim(),
|
|
141
|
+
duration: String(week.duration || "90 minutos").trim(),
|
|
142
|
+
status: String(week.status || "active").trim(),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const allWeeks = Array.from({ length: weeksTotal }, (_, i) => weekInfo(i + 1));
|
|
147
|
+
const activeWeeksRaw = asArray(generation.activeWeeks || config.activeWeeks);
|
|
148
|
+
const activeWeekNumbers = activeWeeksRaw.length
|
|
149
|
+
? activeWeeksRaw.map(Number).filter((n) => Number.isInteger(n) && n >= 1 && n <= weeksTotal)
|
|
150
|
+
: allWeeks.filter((week) => week.status !== "draft" && week.status !== "inactive").map((week) => week.number);
|
|
151
|
+
|
|
152
|
+
const activeWeeks = allWeeks.filter((week) => activeWeekNumbers.includes(week.number));
|
|
153
|
+
const overwriteLaunchers = generation.overwriteLaunchers !== false || force;
|
|
154
|
+
const overwritePortal = generation.overwritePortal !== false || force;
|
|
155
|
+
const overwriteDecks = generation.overwriteDecks !== false || force;
|
|
156
|
+
const overwritePackageScripts = generation.overwritePackageScripts !== false || force;
|
|
157
|
+
const overwriteWeekContent = generation.overwriteWeekContent === true || force;
|
|
158
|
+
const exportPortal = generation.exportPortal === true;
|
|
159
|
+
|
|
160
|
+
const semanaTemplate = readTemplate(
|
|
161
|
+
"plantillas/semana.md",
|
|
162
|
+
`---\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`,
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const launcherTemplate = readTemplate(
|
|
166
|
+
"plantillas/launcher.md",
|
|
167
|
+
`---\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`,
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
function weekTokens(week) {
|
|
171
|
+
return {
|
|
172
|
+
COURSE_SHORT: courseShort,
|
|
173
|
+
COURSE_NAME: courseName,
|
|
174
|
+
COURSE_YEAR: courseYear,
|
|
175
|
+
OPEN_CLASS_LABEL: openClassLabel,
|
|
176
|
+
WEEK_NUMBER: week.number,
|
|
177
|
+
WEEK_TITLE: week.title,
|
|
178
|
+
WEEK_DATE: week.date,
|
|
179
|
+
WEEK_THEME: week.centralTheme,
|
|
180
|
+
WEEK_ACTIVITY: week.activity,
|
|
181
|
+
WEEK_DURATION: week.duration,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function portalWeekItem(week) {
|
|
186
|
+
const slug = `${courseShort}_semana${week.number}`;
|
|
187
|
+
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>`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function buildPortal() {
|
|
191
|
+
const left = activeWeeks.length
|
|
192
|
+
? activeWeeks.filter((_, index) => index < Math.ceil(activeWeeks.length / 2)).map(portalWeekItem).join("\n\n")
|
|
193
|
+
: "### Sin semanas activas\n\nEjecuta `npm run semana -- 1` para generar la primera semana.";
|
|
194
|
+
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.";
|
|
195
|
+
|
|
196
|
+
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`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function buildDecks() {
|
|
200
|
+
const entries = [
|
|
201
|
+
`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 },`,
|
|
202
|
+
];
|
|
203
|
+
|
|
204
|
+
for (const week of activeWeeks) {
|
|
205
|
+
const slug = `${courseShort}_semana${week.number}`;
|
|
206
|
+
entries.push(` {\n name: "${slug}",\n entry: "${slug}.md",\n out: "dist/semanas/${slug}",\n base: withBase("semanas/${slug}/"),\n exportable: true,\n },`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
entries.push(`];\n`);
|
|
210
|
+
return entries.join("\n");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function buildScripts() {
|
|
214
|
+
const scripts = {
|
|
215
|
+
dev: "slidev slides.md --open --port 3000",
|
|
216
|
+
config: "node scripts/generar-desde-config.mjs",
|
|
217
|
+
semana: "node scripts/semana.mjs",
|
|
218
|
+
"pages:check": "node scripts/preparar-github-pages.mjs",
|
|
219
|
+
"actualizar:tema": "npm create openclass-uniminuto@latest . -- --update-theme",
|
|
220
|
+
"actualizar:tema:preview": "npm run actualizar:tema && npm run dev",
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
for (let i = 1; i <= weeksTotal; i++) {
|
|
224
|
+
const slug = `${courseShort}_semana${i}`;
|
|
225
|
+
scripts[`dev:s${i}`] = `slidev ${slug}.md --open --port ${3000 + i}`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
scripts.clean = "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"";
|
|
229
|
+
scripts["clean:downloads"] = "node -e \"require('fs').rmSync('public/descargas',{recursive:true,force:true}); require('fs').mkdirSync('public/descargas',{recursive:true})\"";
|
|
230
|
+
scripts["clean:cache"] = "node -e \"require('fs').rmSync('.slidev',{recursive:true,force:true}); require('fs').rmSync('node_modules/.vite',{recursive:true,force:true})\"";
|
|
231
|
+
scripts["build:index"] = "slidev build slides.md --out dist --base / --without-notes";
|
|
232
|
+
|
|
233
|
+
for (let i = 1; i <= weeksTotal; i++) {
|
|
234
|
+
const slug = `${courseShort}_semana${i}`;
|
|
235
|
+
scripts[`build:s${i}`] = `slidev build ${slug}.md --out dist/semanas/${slug} --base /semanas/${slug}/ --without-notes`;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
scripts["build:all"] = "node scripts/build-site.mjs";
|
|
239
|
+
scripts["build:incremental"] = "node scripts/build-incremental.mjs";
|
|
240
|
+
scripts["export:downloads"] = "node scripts/export-downloads.mjs";
|
|
241
|
+
scripts["export:incremental"] = "node scripts/export-incremental.mjs";
|
|
242
|
+
scripts.vista = "npm run clean && npm run export:downloads && npm run build:all && http-server dist -p 4173 -c-1 -o";
|
|
243
|
+
scripts.publicar = "node scripts/publicar.mjs";
|
|
244
|
+
scripts["dev:todo"] = "node scripts/dev-all.mjs";
|
|
245
|
+
scripts.nuevo = "node scripts/nuevo-curso.mjs";
|
|
246
|
+
scripts["zip:template"] = "node scripts/zip-template.mjs";
|
|
247
|
+
scripts["preview:static"] = "npm run build:all && http-server dist -p 4173 -c-1";
|
|
248
|
+
scripts["preview:pages"] = "npm run export:downloads && npm run build:all && http-server dist -p 4173 -c-1";
|
|
249
|
+
scripts["pages:build"] = "npm run export:downloads && npm run build:all";
|
|
250
|
+
scripts["pages:preview"] = "npm run preview:pages";
|
|
251
|
+
|
|
252
|
+
return scripts;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function ensureProjectAssets() {
|
|
256
|
+
ensureDir("public");
|
|
257
|
+
ensureDir("public/descargas");
|
|
258
|
+
ensureDir("public/imagenes");
|
|
259
|
+
ensureDir("public/videos");
|
|
260
|
+
|
|
261
|
+
const rootFavicon = "public/favicon.png";
|
|
262
|
+
const imageFavicon = "public/imagenes/favicon.png";
|
|
263
|
+
|
|
264
|
+
if (fs.existsSync(rootFavicon) && !fs.existsSync(imageFavicon)) {
|
|
265
|
+
if (!dryRun) fs.copyFileSync(rootFavicon, imageFavicon);
|
|
266
|
+
console.log(`${dryRun ? "🔎" : "✓"} ${imageFavicon} creado desde ${rootFavicon}`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (fs.existsSync(imageFavicon) && !fs.existsSync(rootFavicon)) {
|
|
270
|
+
if (!dryRun) fs.copyFileSync(imageFavicon, rootFavicon);
|
|
271
|
+
console.log(`${dryRun ? "🔎" : "✓"} ${rootFavicon} creado desde ${imageFavicon}`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (!fs.existsSync(rootFavicon) && !fs.existsSync(imageFavicon)) {
|
|
275
|
+
writeBinaryFileIfMissing(rootFavicon, FALLBACK_FAVICON_PNG_BASE64, { label: rootFavicon });
|
|
276
|
+
writeBinaryFileIfMissing(imageFavicon, FALLBACK_FAVICON_PNG_BASE64, { label: imageFavicon });
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function cleanLegacyDemoFiles() {
|
|
281
|
+
const shouldClean = forceCleanDemo || courseShort !== "demo";
|
|
282
|
+
if (!shouldClean) {
|
|
283
|
+
console.log("⏭ Archivos demo conservados.");
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
for (let i = 1; i <= 8; i += 1) {
|
|
288
|
+
removeFileIfExists(`demo_semana${i}.md`, { label: `demo_semana${i}.md` });
|
|
289
|
+
removeFileIfExists(path.join("semanas", `demo_semana${i}.md`), {
|
|
290
|
+
label: path.join("semanas", `demo_semana${i}.md`),
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
console.log("\n┌──────────────────────────────────────────────┐");
|
|
296
|
+
console.log("│ Generador Open Class UNIMINUTO desde config │");
|
|
297
|
+
console.log("└──────────────────────────────────────────────┘\n");
|
|
298
|
+
console.log(`Configuración : ${configPath}`);
|
|
299
|
+
console.log(`Curso : ${courseName}`);
|
|
300
|
+
console.log(`Nombre corto : ${courseShort}`);
|
|
301
|
+
console.log(`Semanas : ${weeksTotal}`);
|
|
302
|
+
console.log(`Activas : ${activeWeeks.map((w) => w.number).join(", ") || "ninguna"}\n`);
|
|
303
|
+
|
|
304
|
+
ensureDir("semanas");
|
|
305
|
+
ensureProjectAssets();
|
|
306
|
+
cleanLegacyDemoFiles();
|
|
307
|
+
|
|
308
|
+
writeFileSafe("slides.md", buildPortal(), { overwrite: overwritePortal, label: "slides.md" });
|
|
309
|
+
writeFileSafe("scripts/decks.mjs", buildDecks(), { overwrite: overwriteDecks, label: "scripts/decks.mjs" });
|
|
310
|
+
|
|
311
|
+
for (const week of allWeeks) {
|
|
312
|
+
const slug = `${courseShort}_semana${week.number}`;
|
|
313
|
+
const tokens = weekTokens(week);
|
|
314
|
+
writeFileSafe(`${slug}.md`, replaceTokens(launcherTemplate, tokens), {
|
|
315
|
+
overwrite: overwriteLaunchers,
|
|
316
|
+
label: `${slug}.md`,
|
|
317
|
+
});
|
|
318
|
+
writeFileSafe(path.join("semanas", `${slug}.md`), replaceTokens(semanaTemplate, tokens), {
|
|
319
|
+
overwrite: overwriteWeekContent,
|
|
320
|
+
label: path.join("semanas", `${slug}.md`),
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (overwritePackageScripts && fs.existsSync("package.json")) {
|
|
325
|
+
const pkg = JSON.parse(fs.readFileSync("package.json", "utf-8"));
|
|
326
|
+
pkg.name = `openclass-${courseShort}`;
|
|
327
|
+
pkg.description = `Presentaciones Open Class del curso ${courseName}.`;
|
|
328
|
+
pkg.scripts = buildScripts();
|
|
329
|
+
writeFileSafe("package.json", JSON.stringify(pkg, null, 2) + "\n", { overwrite: true, label: "package.json" });
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (fs.existsSync("package-lock.json")) {
|
|
333
|
+
try {
|
|
334
|
+
const lock = JSON.parse(fs.readFileSync("package-lock.json", "utf-8"));
|
|
335
|
+
lock.name = `openclass-${courseShort}`;
|
|
336
|
+
if (lock.packages && lock.packages[""]) {
|
|
337
|
+
lock.packages[""].name = `openclass-${courseShort}`;
|
|
338
|
+
}
|
|
339
|
+
writeFileSafe("package-lock.json", JSON.stringify(lock, null, 2) + "\n", { overwrite: true, label: "package-lock.json" });
|
|
340
|
+
} catch {
|
|
341
|
+
console.log("⚠️ package-lock.json no se actualizó porque no parece ser JSON válido.");
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
console.log("\n✅ Configuración generada.");
|
|
346
|
+
console.log(" Próximo paso sugerido: npm install");
|
|
347
|
+
console.log(" Vista de una semana: npm run dev:s1");
|
|
348
|
+
console.log(" Vista completa: npm run vista\n");
|