epicshop 6.85.0 → 6.85.2
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.
|
@@ -4,14 +4,21 @@ import { compileMdx } from '@epic-web/workshop-utils/compile-mdx.server';
|
|
|
4
4
|
import { getErrorMessage } from '@epic-web/workshop-utils/utils';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import { pathExists } from "../../utils/filesystem.js";
|
|
7
|
-
import { collectStepDirectories, formatProductLessonUrl, fetchRemoteWorkshopLessons, isDirectory, resolveMdxFile,
|
|
7
|
+
import { collectStepDirectories, formatProductLessonUrl, fetchRemoteWorkshopLessons, isDirectory, resolveMdxFile, } from "./workshop-content-utils.js";
|
|
8
|
+
function stripEpicAiSlugSuffix(value) {
|
|
9
|
+
// EpicAI embeds sometimes include a `~...` suffix in the slug segment.
|
|
10
|
+
return value.replace(/~[^ ]*$/, '');
|
|
11
|
+
}
|
|
12
|
+
function isProductLessonPathSegment(segment) {
|
|
13
|
+
return segment === 'workshops' || segment === 'tutorials';
|
|
14
|
+
}
|
|
8
15
|
function normalizeHost(host) {
|
|
9
16
|
return host.toLowerCase().replace(/^www\./, '');
|
|
10
17
|
}
|
|
11
|
-
function
|
|
18
|
+
function parseEpicProductSlugFromEmbedUrl(urlString) {
|
|
12
19
|
const parseSegments = (segments) => {
|
|
13
|
-
// Expected: /workshops/<
|
|
14
|
-
if (segments[0]
|
|
20
|
+
// Expected: /workshops/<slug>/... or /tutorials/<slug>/...
|
|
21
|
+
if (!isProductLessonPathSegment(segments[0]))
|
|
15
22
|
return null;
|
|
16
23
|
const workshopSlug = segments[1] ?? null;
|
|
17
24
|
return workshopSlug ? stripEpicAiSlugSuffix(workshopSlug) : null;
|
|
@@ -653,13 +660,13 @@ export async function launchReadiness(options = {}) {
|
|
|
653
660
|
}
|
|
654
661
|
}
|
|
655
662
|
const segments = url.pathname.split('/').filter(Boolean);
|
|
656
|
-
// Expected: /workshops/<workshopSlug>/...
|
|
657
|
-
if (segments[0]
|
|
663
|
+
// Expected: /workshops/<workshopSlug>/... or /tutorials/<tutorialSlug>/...
|
|
664
|
+
if (!isProductLessonPathSegment(segments[0])) {
|
|
658
665
|
for (const file of usedBy) {
|
|
659
666
|
issues.push({
|
|
660
667
|
level: 'warning',
|
|
661
668
|
code: 'epic-video-url-unexpected-path',
|
|
662
|
-
message: 'EpicVideo url path does not start with /workshops/... (this may break progress tracking)',
|
|
669
|
+
message: 'EpicVideo url path does not start with /workshops/... or /tutorials/... (this may break progress tracking)',
|
|
663
670
|
file,
|
|
664
671
|
});
|
|
665
672
|
}
|
|
@@ -690,7 +697,7 @@ export async function launchReadiness(options = {}) {
|
|
|
690
697
|
const lessonSlug = parseEpicLessonSlugFromEmbedUrl(embedUrl);
|
|
691
698
|
if (!lessonSlug)
|
|
692
699
|
continue;
|
|
693
|
-
const workshopSlug =
|
|
700
|
+
const workshopSlug = parseEpicProductSlugFromEmbedUrl(embedUrl);
|
|
694
701
|
if (!workshopSlug || workshopSlug !== productSlug)
|
|
695
702
|
continue;
|
|
696
703
|
try {
|
|
@@ -716,6 +723,7 @@ export async function launchReadiness(options = {}) {
|
|
|
716
723
|
});
|
|
717
724
|
}
|
|
718
725
|
else {
|
|
726
|
+
const remoteModuleType = remote.moduleType;
|
|
719
727
|
const remoteLessons = remote.lessons
|
|
720
728
|
.map((l) => ({
|
|
721
729
|
slug: stripEpicAiSlugSuffix(l.slug),
|
|
@@ -747,6 +755,7 @@ export async function launchReadiness(options = {}) {
|
|
|
747
755
|
return `- ${slug}: ${formatProductLessonUrl({
|
|
748
756
|
productHost,
|
|
749
757
|
productSlug,
|
|
758
|
+
moduleType: remoteModuleType,
|
|
750
759
|
lessonSlug: slug,
|
|
751
760
|
sectionSlug: remoteLesson?.sectionSlug ?? null,
|
|
752
761
|
})}`;
|
|
@@ -763,7 +772,7 @@ export async function launchReadiness(options = {}) {
|
|
|
763
772
|
const lessonSlug = parseEpicLessonSlugFromEmbedUrl(embedUrl);
|
|
764
773
|
if (!lessonSlug)
|
|
765
774
|
continue;
|
|
766
|
-
const workshopSlug =
|
|
775
|
+
const workshopSlug = parseEpicProductSlugFromEmbedUrl(embedUrl);
|
|
767
776
|
if (!workshopSlug || workshopSlug !== productSlug)
|
|
768
777
|
continue;
|
|
769
778
|
try {
|
|
@@ -333,6 +333,7 @@ export async function setVideos(options = {}) {
|
|
|
333
333
|
});
|
|
334
334
|
}
|
|
335
335
|
const remoteLessons = remoteResult.lessons;
|
|
336
|
+
const remoteModuleType = remoteResult.moduleType;
|
|
336
337
|
if (remoteLessons.length === 0) {
|
|
337
338
|
return fail('Product API returned no lessons. Is the workshop published on the product site?', {
|
|
338
339
|
warnings,
|
|
@@ -349,6 +350,7 @@ export async function setVideos(options = {}) {
|
|
|
349
350
|
const lessonUrl = formatProductLessonUrl({
|
|
350
351
|
productHost,
|
|
351
352
|
productSlug,
|
|
353
|
+
moduleType: remoteModuleType,
|
|
352
354
|
lessonSlug: lesson.slug,
|
|
353
355
|
sectionSlug: lesson.sectionSlug,
|
|
354
356
|
});
|
|
@@ -364,6 +366,7 @@ export async function setVideos(options = {}) {
|
|
|
364
366
|
const lessonUrl = formatProductLessonUrl({
|
|
365
367
|
productHost,
|
|
366
368
|
productSlug,
|
|
369
|
+
moduleType: remoteModuleType,
|
|
367
370
|
lessonSlug: lesson.slug,
|
|
368
371
|
sectionSlug: lesson.sectionSlug,
|
|
369
372
|
});
|
|
@@ -385,6 +388,7 @@ export async function setVideos(options = {}) {
|
|
|
385
388
|
const targetUrl = formatProductLessonUrl({
|
|
386
389
|
productHost,
|
|
387
390
|
productSlug,
|
|
391
|
+
moduleType: remoteModuleType,
|
|
388
392
|
lessonSlug: lesson.slug,
|
|
389
393
|
sectionSlug: lesson.sectionSlug,
|
|
390
394
|
});
|
|
@@ -429,6 +433,7 @@ export async function setVideos(options = {}) {
|
|
|
429
433
|
const extras = remoteLessons.slice(requiredLessonSlots).map((lesson) => formatProductLessonUrl({
|
|
430
434
|
productHost,
|
|
431
435
|
productSlug,
|
|
436
|
+
moduleType: remoteModuleType,
|
|
432
437
|
lessonSlug: lesson.slug,
|
|
433
438
|
sectionSlug: lesson.sectionSlug,
|
|
434
439
|
}));
|
|
@@ -3,11 +3,13 @@ export type RemoteLesson = {
|
|
|
3
3
|
slug: string;
|
|
4
4
|
sectionSlug: string | null;
|
|
5
5
|
};
|
|
6
|
+
export type ProductModuleType = 'workshop' | 'tutorial';
|
|
6
7
|
type NormalizeSlug = (value: string) => string;
|
|
7
8
|
export declare function stripEpicAiSlugSuffix(value: string): string;
|
|
8
|
-
export declare function formatProductLessonUrl({ productHost, productSlug, lessonSlug, sectionSlug, }: {
|
|
9
|
+
export declare function formatProductLessonUrl({ productHost, productSlug, moduleType, lessonSlug, sectionSlug, }: {
|
|
9
10
|
productHost: string;
|
|
10
11
|
productSlug: string;
|
|
12
|
+
moduleType?: ProductModuleType;
|
|
11
13
|
lessonSlug: string;
|
|
12
14
|
sectionSlug: string | null;
|
|
13
15
|
}): string;
|
|
@@ -26,6 +28,7 @@ export declare function fetchRemoteWorkshopLessons({ productHost, workshopSlug,
|
|
|
26
28
|
}): Promise<{
|
|
27
29
|
status: 'success';
|
|
28
30
|
lessons: Array<RemoteLesson>;
|
|
31
|
+
moduleType: ProductModuleType;
|
|
29
32
|
} | {
|
|
30
33
|
status: 'error';
|
|
31
34
|
message: string;
|
|
@@ -7,11 +7,12 @@ export function stripEpicAiSlugSuffix(value) {
|
|
|
7
7
|
// EpicAI embeds sometimes include a `~...` suffix in the slug segment.
|
|
8
8
|
return value.replace(/~[^ ]*$/, '');
|
|
9
9
|
}
|
|
10
|
-
export function formatProductLessonUrl({ productHost, productSlug, lessonSlug, sectionSlug, }) {
|
|
10
|
+
export function formatProductLessonUrl({ productHost, productSlug, moduleType = 'workshop', lessonSlug, sectionSlug, }) {
|
|
11
|
+
const productPath = moduleType === 'tutorial' ? 'tutorials' : 'workshops';
|
|
11
12
|
// The product site will typically redirect to a section-specific path when needed.
|
|
12
13
|
return sectionSlug
|
|
13
|
-
? `https://${productHost}
|
|
14
|
-
: `https://${productHost}
|
|
14
|
+
? `https://${productHost}/${productPath}/${productSlug}/${sectionSlug}/${lessonSlug}`
|
|
15
|
+
: `https://${productHost}/${productPath}/${productSlug}/${lessonSlug}`;
|
|
15
16
|
}
|
|
16
17
|
export async function isDirectory(targetPath) {
|
|
17
18
|
try {
|
|
@@ -112,6 +113,12 @@ export async function fetchRemoteWorkshopLessons({ productHost, workshopSlug, no
|
|
|
112
113
|
const resources = data && typeof data === 'object' && 'resources' in data
|
|
113
114
|
? data.resources
|
|
114
115
|
: null;
|
|
116
|
+
const moduleType = data &&
|
|
117
|
+
typeof data === 'object' &&
|
|
118
|
+
'moduleType' in data &&
|
|
119
|
+
data.moduleType === 'tutorial'
|
|
120
|
+
? 'tutorial'
|
|
121
|
+
: 'workshop';
|
|
115
122
|
if (!Array.isArray(resources)) {
|
|
116
123
|
return {
|
|
117
124
|
status: 'error',
|
|
@@ -164,5 +171,5 @@ export async function fetchRemoteWorkshopLessons({ productHost, workshopSlug, no
|
|
|
164
171
|
}
|
|
165
172
|
}
|
|
166
173
|
}
|
|
167
|
-
return { status: 'success', lessons };
|
|
174
|
+
return { status: 'success', lessons, moduleType };
|
|
168
175
|
}
|
package/dist/commands/cleanup.js
CHANGED
|
@@ -630,7 +630,12 @@ export async function cleanup({ silent = false, force = false, targets, workshop
|
|
|
630
630
|
// explicit `--workshops` were provided, we treat the current workshop
|
|
631
631
|
// as the only workshop candidate. This avoids suggesting other
|
|
632
632
|
// workshops in prompts and keeps size estimates accurate.
|
|
633
|
-
|
|
633
|
+
updateSpinner(analysisSpinner, 'Resolving current workshop...');
|
|
634
|
+
const repoWorkshops = await listWorkshopsInDirectory(reposDir);
|
|
635
|
+
const matchingRepoWorkshop = repoWorkshops.find((workshop) => workshop.repoName === workshopFromCwd.repoName);
|
|
636
|
+
// Prefer the repos-directory path when available so workshop IDs are
|
|
637
|
+
// stable even when cwd uses an equivalent symlinked path.
|
|
638
|
+
allWorkshops = [matchingRepoWorkshop ?? workshopFromCwd];
|
|
634
639
|
}
|
|
635
640
|
else {
|
|
636
641
|
updateSpinner(analysisSpinner, 'Finding installed workshops...');
|
|
@@ -655,6 +660,11 @@ export async function cleanup({ silent = false, force = false, targets, workshop
|
|
|
655
660
|
contextWorkshop =
|
|
656
661
|
workshopIdentities.find((workshop) => workshop.id === cwdId) ?? null;
|
|
657
662
|
}
|
|
663
|
+
if (!contextWorkshop &&
|
|
664
|
+
shouldScopeToCwdWorkshop &&
|
|
665
|
+
workshopIdentities.length === 1) {
|
|
666
|
+
contextWorkshop = workshopIdentities[0] ?? null;
|
|
667
|
+
}
|
|
658
668
|
}
|
|
659
669
|
if (selectedTargets.length === 0) {
|
|
660
670
|
if (allWorkshops.length > 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "epicshop",
|
|
3
|
-
"version": "6.85.
|
|
3
|
+
"version": "6.85.2",
|
|
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.85.
|
|
108
|
+
"@epic-web/workshop-utils": "6.85.2",
|
|
109
109
|
"@inquirer/prompts": "^8.2.0",
|
|
110
110
|
"@sentry/node": "^10.38.0",
|
|
111
111
|
"chalk": "^5.6.2",
|