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, stripEpicAiSlugSuffix, } from "./workshop-content-utils.js";
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 parseEpicWorkshopSlugFromEmbedUrl(urlString) {
18
+ function parseEpicProductSlugFromEmbedUrl(urlString) {
12
19
  const parseSegments = (segments) => {
13
- // Expected: /workshops/<workshopSlug>/...
14
- if (segments[0] !== 'workshops')
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] !== 'workshops') {
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 = parseEpicWorkshopSlugFromEmbedUrl(embedUrl);
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 = parseEpicWorkshopSlugFromEmbedUrl(embedUrl);
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}/workshops/${productSlug}/${sectionSlug}/${lessonSlug}`
14
- : `https://${productHost}/workshops/${productSlug}/${lessonSlug}`;
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
  }
@@ -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
- allWorkshops = [workshopFromCwd];
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.0",
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.0",
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",