epicshop 6.84.6 → 6.84.7
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.
|
@@ -12,6 +12,27 @@ function stripEpicAiSlugSuffix(value) {
|
|
|
12
12
|
function normalizeHost(host) {
|
|
13
13
|
return host.toLowerCase().replace(/^www\./, '');
|
|
14
14
|
}
|
|
15
|
+
function parseEpicWorkshopSlugFromEmbedUrl(urlString) {
|
|
16
|
+
const parseSegments = (segments) => {
|
|
17
|
+
// Expected: /workshops/<workshopSlug>/...
|
|
18
|
+
if (segments[0] !== 'workshops')
|
|
19
|
+
return null;
|
|
20
|
+
const workshopSlug = segments[1] ?? null;
|
|
21
|
+
return workshopSlug ? stripEpicAiSlugSuffix(workshopSlug) : null;
|
|
22
|
+
};
|
|
23
|
+
try {
|
|
24
|
+
const url = new URL(urlString);
|
|
25
|
+
const segments = url.pathname.split('/').filter(Boolean);
|
|
26
|
+
return parseSegments(segments);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Fall back to naive parsing (best-effort).
|
|
30
|
+
const withoutHash = urlString.split('#')[0] ?? urlString;
|
|
31
|
+
const withoutQuery = withoutHash.split('?')[0] ?? withoutHash;
|
|
32
|
+
const segments = withoutQuery.split('/').filter(Boolean);
|
|
33
|
+
return parseSegments(segments);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
15
36
|
function parseEpicLessonSlugFromEmbedUrl(urlString) {
|
|
16
37
|
const parseSegments = (segments) => {
|
|
17
38
|
if (segments.length === 0)
|
|
@@ -38,6 +59,10 @@ function parseEpicLessonSlugFromEmbedUrl(urlString) {
|
|
|
38
59
|
return parseSegments(segments);
|
|
39
60
|
}
|
|
40
61
|
}
|
|
62
|
+
function formatProductLessonUrl({ productHost, productSlug, lessonSlug, }) {
|
|
63
|
+
// The product site will typically redirect to a section-specific path when needed.
|
|
64
|
+
return `https://${productHost}/workshops/${productSlug}/${lessonSlug}`;
|
|
65
|
+
}
|
|
41
66
|
function formatIssue(issue, workshopRoot) {
|
|
42
67
|
const icon = issue.level === 'error' ? chalk.red('❌') : chalk.yellow('⚠️ ');
|
|
43
68
|
const filePart = issue.file
|
|
@@ -789,14 +814,30 @@ export async function launchReadiness(options = {}) {
|
|
|
789
814
|
// -------------------------------------------------------
|
|
790
815
|
// 4) Remote product lesson list vs local embedded videos
|
|
791
816
|
// -------------------------------------------------------
|
|
792
|
-
const localLessonSlugs = new Set();
|
|
793
|
-
for (const embedUrl of embedOccurrences.keys()) {
|
|
794
|
-
const slug = parseEpicLessonSlugFromEmbedUrl(embedUrl);
|
|
795
|
-
if (slug)
|
|
796
|
-
localLessonSlugs.add(slug);
|
|
797
|
-
}
|
|
798
817
|
if (!skipRemote) {
|
|
799
818
|
if (productHost && productSlug) {
|
|
819
|
+
// Only consider embeds that belong to this workshop on the configured host.
|
|
820
|
+
// It's valid for content to include EpicVideo embeds from other workshops/paths.
|
|
821
|
+
const localProductLessonSlugs = new Set();
|
|
822
|
+
const normalizedConfigHost = normalizeHost(productHost);
|
|
823
|
+
for (const embedUrl of embedOccurrences.keys()) {
|
|
824
|
+
const lessonSlug = parseEpicLessonSlugFromEmbedUrl(embedUrl);
|
|
825
|
+
if (!lessonSlug)
|
|
826
|
+
continue;
|
|
827
|
+
const workshopSlug = parseEpicWorkshopSlugFromEmbedUrl(embedUrl);
|
|
828
|
+
if (!workshopSlug || workshopSlug !== productSlug)
|
|
829
|
+
continue;
|
|
830
|
+
try {
|
|
831
|
+
const url = new URL(embedUrl);
|
|
832
|
+
if (normalizeHost(url.host) !== normalizedConfigHost)
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
835
|
+
catch {
|
|
836
|
+
// Invalid URLs are reported elsewhere (host/path validation); ignore here.
|
|
837
|
+
continue;
|
|
838
|
+
}
|
|
839
|
+
localProductLessonSlugs.add(lessonSlug);
|
|
840
|
+
}
|
|
800
841
|
const remote = await fetchRemoteWorkshopLessonSlugs({
|
|
801
842
|
productHost,
|
|
802
843
|
workshopSlug: productSlug,
|
|
@@ -817,25 +858,48 @@ export async function launchReadiness(options = {}) {
|
|
|
817
858
|
message: 'Product API returned no lessons. Is the workshop published on the product site?',
|
|
818
859
|
});
|
|
819
860
|
}
|
|
820
|
-
const missing = remoteLessonSlugs.filter((slug) => !
|
|
861
|
+
const missing = remoteLessonSlugs.filter((slug) => !localProductLessonSlugs.has(slug));
|
|
821
862
|
if (missing.length) {
|
|
863
|
+
const formatted = missing
|
|
864
|
+
.sort()
|
|
865
|
+
.map((slug) => `- ${slug}: ${formatProductLessonUrl({
|
|
866
|
+
productHost,
|
|
867
|
+
productSlug,
|
|
868
|
+
lessonSlug: slug,
|
|
869
|
+
})}`)
|
|
870
|
+
.join('\n');
|
|
822
871
|
issues.push({
|
|
823
872
|
level: 'error',
|
|
824
873
|
code: 'missing-product-videos-in-workshop',
|
|
825
|
-
message: `Missing videos in workshop for product lessons
|
|
826
|
-
.sort()
|
|
827
|
-
.join(', ')}`,
|
|
874
|
+
message: `Missing videos in workshop for product lessons:\n${formatted}`,
|
|
828
875
|
});
|
|
829
876
|
}
|
|
830
|
-
const
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
877
|
+
const remoteLessonSlugSet = new Set(remoteLessonSlugs);
|
|
878
|
+
for (const [embedUrl, usedBy] of embedOccurrences.entries()) {
|
|
879
|
+
const lessonSlug = parseEpicLessonSlugFromEmbedUrl(embedUrl);
|
|
880
|
+
if (!lessonSlug)
|
|
881
|
+
continue;
|
|
882
|
+
const workshopSlug = parseEpicWorkshopSlugFromEmbedUrl(embedUrl);
|
|
883
|
+
if (!workshopSlug || workshopSlug !== productSlug)
|
|
884
|
+
continue;
|
|
885
|
+
try {
|
|
886
|
+
const url = new URL(embedUrl);
|
|
887
|
+
if (normalizeHost(url.host) !== normalizedConfigHost)
|
|
888
|
+
continue;
|
|
889
|
+
}
|
|
890
|
+
catch {
|
|
891
|
+
continue;
|
|
892
|
+
}
|
|
893
|
+
if (remoteLessonSlugSet.has(lessonSlug))
|
|
894
|
+
continue;
|
|
895
|
+
for (const file of usedBy) {
|
|
896
|
+
issues.push({
|
|
897
|
+
level: 'warning',
|
|
898
|
+
code: 'extra-local-videos',
|
|
899
|
+
message: `EpicVideo embed not present in the product lesson list: ${embedUrl}`,
|
|
900
|
+
file,
|
|
901
|
+
});
|
|
902
|
+
}
|
|
839
903
|
}
|
|
840
904
|
}
|
|
841
905
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "epicshop",
|
|
3
|
-
"version": "6.84.
|
|
3
|
+
"version": "6.84.7",
|
|
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.84.
|
|
108
|
+
"@epic-web/workshop-utils": "6.84.7",
|
|
109
109
|
"@inquirer/prompts": "^8.2.0",
|
|
110
110
|
"@sentry/node": "^10.38.0",
|
|
111
111
|
"chalk": "^5.6.2",
|