From a26419b113eb540e50333ee1d870b842f25c389c Mon Sep 17 00:00:00 2001
From: eric-burel <eb@lbke.fr>
Date: Mon, 28 Oct 2024 18:42:58 +0100
Subject: [PATCH] access current lesson from Astro.locals

---
 e2e/src/components/CurrentEntry.astro         | 29 +++++++++++++++++++
 .../tests/current-entry/basic/content.mdx     | 10 +++++++
 .../tutorial/tests/current-entry/meta.md      |  4 +++
 e2e/src/env.d.ts                              | 10 +++++++
 e2e/test/current-entry.test.ts                | 13 +++++++++
 packages/astro/src/default/env-default.d.ts   |  8 +++++
 .../astro/src/default/pages/[...slug].astro   |  1 +
 packages/astro/src/default/utils/content.ts   |  3 +-
 packages/types/src/entities/index.ts          |  3 ++
 9 files changed, 80 insertions(+), 1 deletion(-)
 create mode 100644 e2e/src/components/CurrentEntry.astro
 create mode 100644 e2e/src/content/tutorial/tests/current-entry/basic/content.mdx
 create mode 100644 e2e/src/content/tutorial/tests/current-entry/meta.md
 create mode 100644 e2e/test/current-entry.test.ts

diff --git a/e2e/src/components/CurrentEntry.astro b/e2e/src/components/CurrentEntry.astro
new file mode 100644
index 000000000..7132b66a2
--- /dev/null
+++ b/e2e/src/components/CurrentEntry.astro
@@ -0,0 +1,29 @@
+---
+import file from '@content/tutorial/tests/file-tree/allow-edits-disabled/_files/first-level/file';
+import { getCollection } from 'astro:content';
+import { getEntry, getEntries } from 'astro:content';
+
+if (!Astro.locals.tk) {
+  throw new Error('Not in the context of a lesson, Astro.locals.tk is not defined');
+}
+if (!Astro.locals.tk.lesson) {
+  throw new Error('Lesson not set in tutorialkit Astro.locals.tk context');
+}
+
+const { entrySlug } = Astro.locals.tk.lesson;
+
+const currentEntry = await getEntry('tutorial', entrySlug);
+if (!currentEntry) {
+  throw new Error(`Entry not found for slug: ${entrySlug}.`);
+}
+---
+
+<div>
+  <h2>Lesson</h2>
+  {
+    // @ts-ignore
+    JSON.stringify(Astro.locals.tk.lesson, null, 2)
+  }
+  <h2>Entry</h2>
+  {JSON.stringify(currentEntry, null, 2)}
+</div>
diff --git a/e2e/src/content/tutorial/tests/current-entry/basic/content.mdx b/e2e/src/content/tutorial/tests/current-entry/basic/content.mdx
new file mode 100644
index 000000000..bd54903e9
--- /dev/null
+++ b/e2e/src/content/tutorial/tests/current-entry/basic/content.mdx
@@ -0,0 +1,10 @@
+---
+type: lesson
+title: Basic
+terminal:
+  panels: terminal
+---
+
+import CurrentEntry from "@components/CurrentEntry.astro"
+
+<CurrentEntry />
\ No newline at end of file
diff --git a/e2e/src/content/tutorial/tests/current-entry/meta.md b/e2e/src/content/tutorial/tests/current-entry/meta.md
new file mode 100644
index 000000000..ff89ab9ea
--- /dev/null
+++ b/e2e/src/content/tutorial/tests/current-entry/meta.md
@@ -0,0 +1,4 @@
+---
+type: chapter
+title: Current Entry
+---
diff --git a/e2e/src/env.d.ts b/e2e/src/env.d.ts
index 9505823a5..07d677d45 100644
--- a/e2e/src/env.d.ts
+++ b/e2e/src/env.d.ts
@@ -1,3 +1,13 @@
 /// <reference path="../.astro/types.d.ts" />
 /// <reference types="@tutorialkit/astro/types" />
 /// <reference types="astro/client" />
+
+// copied from packages/astro/src/default/env-default.d.ts
+// TODO: should probably be exposed by astro/types instead?
+declare namespace App {
+  interface Locals {
+    tk: {
+      lesson: import('@tutorialkit/types').Lesson<any>;
+    };
+  }
+}
diff --git a/e2e/test/current-entry.test.ts b/e2e/test/current-entry.test.ts
new file mode 100644
index 000000000..47ce4694a
--- /dev/null
+++ b/e2e/test/current-entry.test.ts
@@ -0,0 +1,13 @@
+import { test, expect } from '@playwright/test';
+
+const BASE_URL = '/tests/current-entry';
+
+test('developer can access current lesson and collection entry from Astro.locals', async ({ page }) => {
+  await page.goto(`${BASE_URL}/basic`);
+
+  // lesson id
+  await expect(page.getByText('"id": "basic"')).toBeVisible();
+
+  // astro collection entry id
+  await expect(page.getByText('"id": "tests/current-entry/basic/content.mdx"')).toBeVisible();
+});
diff --git a/packages/astro/src/default/env-default.d.ts b/packages/astro/src/default/env-default.d.ts
index 303066a5a..d8f085fa1 100644
--- a/packages/astro/src/default/env-default.d.ts
+++ b/packages/astro/src/default/env-default.d.ts
@@ -17,3 +17,11 @@ declare module 'tutorialkit:override-components' {
 
 declare const __ENTERPRISE__: boolean;
 declare const __WC_CONFIG__: WebContainerConfig | undefined;
+
+declare namespace App {
+  interface Locals {
+    tk: {
+      lesson: import('@tutorialkit/types').Lesson<any>;
+    };
+  }
+}
diff --git a/packages/astro/src/default/pages/[...slug].astro b/packages/astro/src/default/pages/[...slug].astro
index b03f55d74..0d610369f 100644
--- a/packages/astro/src/default/pages/[...slug].astro
+++ b/packages/astro/src/default/pages/[...slug].astro
@@ -20,6 +20,7 @@ const meta = lesson.data?.meta ?? {};
 // use lesson's default title and a default description for SEO metadata
 meta.title ??= title;
 meta.description ??= 'A TutorialKit interactive lesson';
+Astro.locals.tk = { lesson };
 ---
 
 <Layout title={title} meta={meta}>
diff --git a/packages/astro/src/default/utils/content.ts b/packages/astro/src/default/utils/content.ts
index b1665ba5b..e72b26e62 100644
--- a/packages/astro/src/default/utils/content.ts
+++ b/packages/astro/src/default/utils/content.ts
@@ -18,7 +18,7 @@ export async function getTutorial(): Promise<Tutorial> {
   let lessons: Lesson[] = [];
 
   for (const entry of collection) {
-    const { id, data } = entry;
+    const { id, data, slug: entrySlug } = entry;
     const { type } = data;
 
     const [partId, chapterId, lessonId] = id.split('/');
@@ -74,6 +74,7 @@ export async function getTutorial(): Promise<Tutorial> {
         data,
         id: lessonId,
         filepath: id,
+        entrySlug,
         order: -1,
         part: {
           id: partId,
diff --git a/packages/types/src/entities/index.ts b/packages/types/src/entities/index.ts
index 3d0abf5f6..a0fdaa83f 100644
--- a/packages/types/src/entities/index.ts
+++ b/packages/types/src/entities/index.ts
@@ -45,6 +45,9 @@ export interface Lesson<T = unknown> {
   part: { id: string; title: string };
   chapter: { id: string; title: string };
   slug: string;
+
+  // slug to pass to astro:content `getEntry`
+  entrySlug: string;
   filepath: string;
   editPageLink?: string;
   files: FilesRefList;