epicshop 6.86.0 → 6.87.0
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.
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { debuglog } from 'node:util';
|
|
3
4
|
import { compileMdx } from '@epic-web/workshop-utils/compile-mdx.server';
|
|
4
5
|
import { getErrorMessage } from '@epic-web/workshop-utils/utils';
|
|
5
6
|
import chalk from 'chalk';
|
|
6
7
|
import { pathExists } from "../../utils/filesystem.js";
|
|
7
8
|
import { collectStepDirectories, formatProductLessonUrl, fetchRemoteWorkshopLessons, isDirectory, resolveMdxFile, } from "./workshop-content-utils.js";
|
|
9
|
+
const debug = debuglog('epic:launch-readiness');
|
|
8
10
|
function stripEpicAiSlugSuffix(value) {
|
|
9
11
|
// EpicAI embeds sometimes include a `~...` suffix in the slug segment.
|
|
10
12
|
return value.replace(/~[^ ]*$/, '');
|
|
@@ -62,6 +64,27 @@ function parseEpicLessonSlugFromEmbedUrl(urlString) {
|
|
|
62
64
|
return parseSegments(segments);
|
|
63
65
|
}
|
|
64
66
|
}
|
|
67
|
+
function parseEmbedUrlVariant(urlString) {
|
|
68
|
+
const parseSegments = (segments) => {
|
|
69
|
+
const last = segments.at(-1) ?? '';
|
|
70
|
+
return {
|
|
71
|
+
hasProblemSuffix: last === 'problem',
|
|
72
|
+
hasSolutionSuffix: last === 'solution',
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
try {
|
|
76
|
+
const url = new URL(urlString);
|
|
77
|
+
const segments = url.pathname.split('/').filter(Boolean);
|
|
78
|
+
return parseSegments(segments);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// Fall back to naive parsing (best-effort).
|
|
82
|
+
const withoutHash = urlString.split('#')[0] ?? urlString;
|
|
83
|
+
const withoutQuery = withoutHash.split('?')[0] ?? withoutHash;
|
|
84
|
+
const segments = withoutQuery.split('/').filter(Boolean);
|
|
85
|
+
return parseSegments(segments);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
65
88
|
function formatIssue(issue, workshopRoot) {
|
|
66
89
|
const icon = issue.level === 'error' ? chalk.red('❌') : chalk.yellow('⚠️ ');
|
|
67
90
|
const filePart = issue.file
|
|
@@ -569,6 +592,7 @@ export async function launchReadiness(options = {}) {
|
|
|
569
592
|
try {
|
|
570
593
|
const compiled = await compileMdx(file.fullPath);
|
|
571
594
|
const embeds = compiled.epicVideoEmbeds ?? [];
|
|
595
|
+
debug('file=%s kind=%s embeds=%d', file.relativePath, file.kind, embeds.length);
|
|
572
596
|
if (embeds.length === 0) {
|
|
573
597
|
issues.push({
|
|
574
598
|
level: 'error',
|
|
@@ -579,6 +603,24 @@ export async function launchReadiness(options = {}) {
|
|
|
579
603
|
continue;
|
|
580
604
|
}
|
|
581
605
|
for (const embed of embeds) {
|
|
606
|
+
const variant = parseEmbedUrlVariant(embed);
|
|
607
|
+
debug('embed file=%s kind=%s url=%s problemSuffix=%s solutionSuffix=%s', file.relativePath, file.kind, embed, variant.hasProblemSuffix, variant.hasSolutionSuffix);
|
|
608
|
+
if (file.kind === 'step-solution' && !variant.hasSolutionSuffix) {
|
|
609
|
+
issues.push({
|
|
610
|
+
level: 'error',
|
|
611
|
+
code: 'step-solution-video-missing-solution-suffix',
|
|
612
|
+
message: `Step solution files must use a /solution video URL suffix: ${embed}`,
|
|
613
|
+
file: file.fullPath,
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
if (file.kind === 'step-problem' && variant.hasSolutionSuffix) {
|
|
617
|
+
issues.push({
|
|
618
|
+
level: 'error',
|
|
619
|
+
code: 'step-problem-video-uses-solution-suffix',
|
|
620
|
+
message: `Step problem files must not use a /solution video URL suffix: ${embed}`,
|
|
621
|
+
file: file.fullPath,
|
|
622
|
+
});
|
|
623
|
+
}
|
|
582
624
|
const set = embedOccurrences.get(embed) ?? new Set();
|
|
583
625
|
set.add(file.fullPath);
|
|
584
626
|
embedOccurrences.set(embed, set);
|
|
@@ -711,6 +753,7 @@ export async function launchReadiness(options = {}) {
|
|
|
711
753
|
}
|
|
712
754
|
localProductLessonSlugs.add(lessonSlug);
|
|
713
755
|
}
|
|
756
|
+
debug('remote-check workshop=%s localProductLessonSlugs=%d', productSlug, localProductLessonSlugs.size);
|
|
714
757
|
const remote = await fetchRemoteWorkshopLessons({
|
|
715
758
|
productHost,
|
|
716
759
|
workshopSlug: productSlug,
|
|
@@ -739,6 +782,7 @@ export async function launchReadiness(options = {}) {
|
|
|
739
782
|
remoteLessonBySlug.set(l.slug, l);
|
|
740
783
|
}
|
|
741
784
|
const remoteLessonSlugs = [...remoteLessonBySlug.keys()];
|
|
785
|
+
debug('remote-check workshop=%s remoteLessonSlugs=%d moduleType=%s', productSlug, remoteLessonSlugs.length, remoteModuleType);
|
|
742
786
|
if (remoteLessonSlugs.length === 0) {
|
|
743
787
|
issues.push({
|
|
744
788
|
level: 'error',
|
|
@@ -748,6 +792,7 @@ export async function launchReadiness(options = {}) {
|
|
|
748
792
|
}
|
|
749
793
|
const missing = remoteLessonSlugs.filter((slug) => !localProductLessonSlugs.has(slug));
|
|
750
794
|
if (missing.length) {
|
|
795
|
+
debug('remote-check missing-lesson-slugs=%o', missing.slice().sort());
|
|
751
796
|
const formatted = missing
|
|
752
797
|
.sort()
|
|
753
798
|
.map((slug) => {
|
|
@@ -234,6 +234,12 @@ function upsertTitleEpicVideo({ content, url, }) {
|
|
|
234
234
|
function formatNumberedList(items, { startAt = 1 } = {}) {
|
|
235
235
|
return items.map((item, index) => `${startAt + index}. ${item}`).join('\n');
|
|
236
236
|
}
|
|
237
|
+
function getTargetLessonUrl({ baseUrl, fileKind, }) {
|
|
238
|
+
if (fileKind === 'step-solution') {
|
|
239
|
+
return `${baseUrl.replace(/\/+$/, '')}/solution`;
|
|
240
|
+
}
|
|
241
|
+
return baseUrl;
|
|
242
|
+
}
|
|
237
243
|
function buildFileLessonSlotPlans(files) {
|
|
238
244
|
const plans = [];
|
|
239
245
|
const stepSlotByKey = new Map();
|
|
@@ -385,13 +391,17 @@ export async function setVideos(options = {}) {
|
|
|
385
391
|
const lesson = remoteLessons[plan.lessonSlotIndex];
|
|
386
392
|
if (!lesson)
|
|
387
393
|
continue;
|
|
388
|
-
const
|
|
394
|
+
const baseLessonUrl = formatProductLessonUrl({
|
|
389
395
|
productHost,
|
|
390
396
|
productSlug,
|
|
391
397
|
moduleType: remoteModuleType,
|
|
392
398
|
lessonSlug: lesson.slug,
|
|
393
399
|
sectionSlug: lesson.sectionSlug,
|
|
394
400
|
});
|
|
401
|
+
const targetUrl = getTargetLessonUrl({
|
|
402
|
+
baseUrl: baseLessonUrl,
|
|
403
|
+
fileKind: plan.file.kind,
|
|
404
|
+
});
|
|
395
405
|
let currentContent = '';
|
|
396
406
|
try {
|
|
397
407
|
currentContent = await fs.readFile(plan.file.fullPath, 'utf8');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "epicshop",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.87.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
"build:watch": "nx watch --projects=epicshop -- nx run \\$NX_PROJECT_NAME:build"
|
|
106
106
|
},
|
|
107
107
|
"dependencies": {
|
|
108
|
-
"@epic-web/workshop-utils": "6.
|
|
108
|
+
"@epic-web/workshop-utils": "6.87.0",
|
|
109
109
|
"@inquirer/prompts": "^8.2.0",
|
|
110
110
|
"@sentry/node": "^10.38.0",
|
|
111
111
|
"chalk": "^5.6.2",
|