@epic-web/workshop-utils 4.0.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.
Files changed (59) hide show
  1. package/README.md +3 -0
  2. package/dist/esm/apps.server.d.ts +3861 -0
  3. package/dist/esm/apps.server.d.ts.map +1 -0
  4. package/dist/esm/apps.server.js +1011 -0
  5. package/dist/esm/apps.server.js.map +1 -0
  6. package/dist/esm/cache.server.d.ts +798 -0
  7. package/dist/esm/cache.server.d.ts.map +1 -0
  8. package/dist/esm/cache.server.js +113 -0
  9. package/dist/esm/cache.server.js.map +1 -0
  10. package/dist/esm/change-tracker.server.d.ts +8 -0
  11. package/dist/esm/change-tracker.server.d.ts.map +1 -0
  12. package/dist/esm/change-tracker.server.js +32 -0
  13. package/dist/esm/change-tracker.server.js.map +1 -0
  14. package/dist/esm/codefile-mdx.server.d.ts +16 -0
  15. package/dist/esm/codefile-mdx.server.d.ts.map +1 -0
  16. package/dist/esm/codefile-mdx.server.js +275 -0
  17. package/dist/esm/codefile-mdx.server.js.map +1 -0
  18. package/dist/esm/compile-mdx.server.d.ts +11 -0
  19. package/dist/esm/compile-mdx.server.d.ts.map +1 -0
  20. package/dist/esm/compile-mdx.server.js +330 -0
  21. package/dist/esm/compile-mdx.server.js.map +1 -0
  22. package/dist/esm/db.server.d.ts +176 -0
  23. package/dist/esm/db.server.d.ts.map +1 -0
  24. package/dist/esm/db.server.js +203 -0
  25. package/dist/esm/db.server.js.map +1 -0
  26. package/dist/esm/git.server.d.ts +27 -0
  27. package/dist/esm/git.server.d.ts.map +1 -0
  28. package/dist/esm/git.server.js +93 -0
  29. package/dist/esm/git.server.js.map +1 -0
  30. package/dist/esm/iframe-sync.d.ts +10 -0
  31. package/dist/esm/iframe-sync.d.ts.map +1 -0
  32. package/dist/esm/iframe-sync.js +101 -0
  33. package/dist/esm/iframe-sync.js.map +1 -0
  34. package/dist/esm/package.json +3 -0
  35. package/dist/esm/playwright.server.d.ts +6 -0
  36. package/dist/esm/playwright.server.d.ts.map +1 -0
  37. package/dist/esm/playwright.server.js +94 -0
  38. package/dist/esm/playwright.server.js.map +1 -0
  39. package/dist/esm/process-manager.server.d.ts +78 -0
  40. package/dist/esm/process-manager.server.d.ts.map +1 -0
  41. package/dist/esm/process-manager.server.js +267 -0
  42. package/dist/esm/process-manager.server.js.map +1 -0
  43. package/dist/esm/test.d.ts +9 -0
  44. package/dist/esm/test.d.ts.map +1 -0
  45. package/dist/esm/test.js +45 -0
  46. package/dist/esm/test.js.map +1 -0
  47. package/dist/esm/timing.server.d.ts +20 -0
  48. package/dist/esm/timing.server.d.ts.map +1 -0
  49. package/dist/esm/timing.server.js +89 -0
  50. package/dist/esm/timing.server.js.map +1 -0
  51. package/dist/esm/utils.d.ts +2 -0
  52. package/dist/esm/utils.d.ts.map +1 -0
  53. package/dist/esm/utils.js +13 -0
  54. package/dist/esm/utils.js.map +1 -0
  55. package/dist/esm/utils.server.d.ts +3 -0
  56. package/dist/esm/utils.server.d.ts.map +1 -0
  57. package/dist/esm/utils.server.js +32 -0
  58. package/dist/esm/utils.server.js.map +1 -0
  59. package/package.json +171 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.server.d.ts","sourceRoot":"","sources":["../../src/cache.server.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,CAAC,MAAM,qBAAqB,CAAA;AACxC,OAAO,EAEN,KAAK,KAAK,IAAI,cAAc,EAE5B,MAAM,qBAAqB,CAAA;AAY5B,OAAO,EAA2B,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAE1E,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CACuB,CAAA;AACpD,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAoD,CAAA;AAChF,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAoD,CAAA;AAChF,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CACyB,CAAA;AACxD,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAuC,CAAA;AAC7D,eAAO,MAAM,aAAa;;;;;CAA8C,CAAA;AACxE,eAAO,MAAM,cAAc;;;;;CAA+C,CAAA;AAC1E,eAAO,MAAM,qBAAqB;;;;;CAEjC,CAAA;AACD,MAAM,MAAM,uBAAuB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;AAC9D,eAAO,MAAM,kBAAkB;;;;;CAER,CAAA;AACvB,eAAO,MAAM,iBAAiB;;;;;CAAkD,CAAA;AAIhF,eAAO,MAAM,OAAO,EAAE,cA4BrB,CAAA;AAED,wBAAsB,WAAW,8BAUhC;AAED,wBAAgB,kBAAkB,CAAC,cAAc,EAAE,IAAI,EAAE,MAAM;;;;;EAsB9D;AAED,wBAAsB,SAAS,CAAC,KAAK,EAAE,EACtC,OAAO,EACP,OAAO,EACP,GAAG,EAGH,SAA2E,EAC3E,GAAG,OAAO,EACV,EAAE,IAAI,CAAC,CAAC,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,YAAY,CAAC,GAAG;IAClD,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,UAAU,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAA;CAClB,GAAG,OAAO,CAAC,KAAK,CAAC,CAgBjB;AAED,wBAAsB,gBAAgB,CAAC,EACtC,UAAU,EACV,OAAO,EACP,GAAG,GACH,EAAE;IACF,UAAU,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,GAAG,CAAC,EAAE,MAAM,CAAA;CACZ,oBAaA"}
@@ -0,0 +1,113 @@
1
+ import os from 'os';
2
+ import path from 'path';
3
+ import * as C from '@epic-web/cachified';
4
+ import { verboseReporter, } from '@epic-web/cachified';
5
+ import { remember } from '@epic-web/remember';
6
+ import fsExtra from 'fs-extra';
7
+ import { LRUCache } from 'lru-cache';
8
+ import md5 from 'md5-hex';
9
+ import { cachifiedTimingReporter } from './timing.server.js';
10
+ export const solutionAppCache = makeSingletonCache('SolutionAppCache');
11
+ export const problemAppCache = makeSingletonCache('ProblemAppCache');
12
+ export const exampleAppCache = makeSingletonCache('ExampleAppCache');
13
+ export const playgroundAppCache = makeSingletonCache('PlaygroundAppCache');
14
+ export const appsCache = makeSingletonCache('AppsCache');
15
+ export const diffCodeCache = makeSingletonCache('DiffCodeCache');
16
+ export const diffFilesCache = makeSingletonCache('DiffFilesCache');
17
+ export const compiledMarkdownCache = makeSingletonCache('CompiledMarkdownCache');
18
+ export const embeddedFilesCache = makeSingletonCache('EmbeddedFilesCache');
19
+ export const compiledCodeCache = makeSingletonCache('CompiledCodeCache');
20
+ const cacheDir = path.join(os.homedir(), '.epicshop', 'cache');
21
+ export const fsCache = {
22
+ name: 'Filesystem cache',
23
+ async get(key) {
24
+ try {
25
+ const filePath = path.join(cacheDir, md5(key));
26
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
27
+ const data = await fsExtra.readJSON(filePath);
28
+ return data;
29
+ }
30
+ catch (error) {
31
+ if (error instanceof Error &&
32
+ 'code' in error &&
33
+ error.code === 'ENOENT') {
34
+ return null;
35
+ }
36
+ throw error;
37
+ }
38
+ },
39
+ async set(key, entry) {
40
+ const filePath = path.join(cacheDir, md5(key));
41
+ await fsExtra.ensureDir(path.dirname(filePath));
42
+ await fsExtra.writeJSON(filePath, entry);
43
+ },
44
+ async delete(key) {
45
+ const filePath = path.join(cacheDir, md5(key));
46
+ await fsExtra.remove(filePath);
47
+ },
48
+ };
49
+ export async function deleteCache() {
50
+ if (process.env.EPICSHOP_DEPLOYED)
51
+ return null;
52
+ try {
53
+ if (await fsExtra.exists(cacheDir)) {
54
+ await fsExtra.remove(cacheDir);
55
+ }
56
+ }
57
+ catch (error) {
58
+ console.error(`Error deleting the cache in ${cacheDir}`, error);
59
+ }
60
+ }
61
+ export function makeSingletonCache(name) {
62
+ return remember(name, () => {
63
+ const lruInstance = new LRUCache({
64
+ max: 1000,
65
+ });
66
+ const lru = {
67
+ name,
68
+ set: (key, value) => {
69
+ const ttl = C.totalTtl(value.metadata);
70
+ lruInstance.set(key, value, {
71
+ ttl: ttl === Infinity ? undefined : ttl,
72
+ start: value.metadata.createdTime,
73
+ });
74
+ return value;
75
+ },
76
+ get: key => lruInstance.get(key),
77
+ delete: key => lruInstance.delete(key),
78
+ };
79
+ return lru;
80
+ });
81
+ }
82
+ export async function cachified({ request, timings, key,
83
+ // TODO: figure out what this was for before...
84
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
85
+ timingKey = key.length > 18 ? `${key.slice(0, 7)}...${key.slice(-8)}` : key, ...options }) {
86
+ return C.cachified({
87
+ ...options,
88
+ key,
89
+ forceFresh: await shouldForceFresh({
90
+ forceFresh: options.forceFresh,
91
+ request,
92
+ key,
93
+ }),
94
+ }, C.mergeReporters(cachifiedTimingReporter(timings), process.env.EPICSHOP_DEBUG_CACHE ? verboseReporter() : undefined));
95
+ }
96
+ export async function shouldForceFresh({ forceFresh, request, key, }) {
97
+ if (typeof forceFresh === 'boolean')
98
+ return forceFresh;
99
+ if (typeof forceFresh === 'string' && key) {
100
+ return forceFresh.split(',').includes(key);
101
+ }
102
+ if (!request)
103
+ return false;
104
+ const fresh = new URL(request.url).searchParams.get('fresh');
105
+ if (typeof fresh !== 'string')
106
+ return false;
107
+ if (fresh === '')
108
+ return true;
109
+ if (!key)
110
+ return false;
111
+ return fresh.split(',').includes(key);
112
+ }
113
+ //# sourceMappingURL=cache.server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.server.js","sourceRoot":"","sources":["../../src/cache.server.ts"],"names":[],"mappings":"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","sourcesContent":["import os from 'os'\nimport path from 'path'\nimport * as C from '@epic-web/cachified'\nimport {\n\tverboseReporter,\n\ttype Cache as CachifiedCache,\n\ttype CacheEntry,\n} from '@epic-web/cachified'\nimport { remember } from '@epic-web/remember'\nimport fsExtra from 'fs-extra'\nimport { LRUCache } from 'lru-cache'\nimport md5 from 'md5-hex'\nimport {\n\ttype SolutionApp,\n\ttype App,\n\ttype PlaygroundApp,\n\ttype ProblemApp,\n\ttype ExampleApp,\n} from './apps.server.js'\nimport { cachifiedTimingReporter, type Timings } from './timing.server.js'\n\nexport const solutionAppCache =\n\tmakeSingletonCache<SolutionApp>('SolutionAppCache')\nexport const problemAppCache = makeSingletonCache<ProblemApp>('ProblemAppCache')\nexport const exampleAppCache = makeSingletonCache<ExampleApp>('ExampleAppCache')\nexport const playgroundAppCache =\n\tmakeSingletonCache<PlaygroundApp>('PlaygroundAppCache')\nexport const appsCache = makeSingletonCache<App>('AppsCache')\nexport const diffCodeCache = makeSingletonCache<string>('DiffCodeCache')\nexport const diffFilesCache = makeSingletonCache<string>('DiffFilesCache')\nexport const compiledMarkdownCache = makeSingletonCache<string>(\n\t'CompiledMarkdownCache',\n)\nexport type CachedEmbeddedFilesList = Record<string, string[]>\nexport const embeddedFilesCache = makeSingletonCache<\n\tCachedEmbeddedFilesList | undefined\n>('EmbeddedFilesCache')\nexport const compiledCodeCache = makeSingletonCache<string>('CompiledCodeCache')\n\nconst cacheDir = path.join(os.homedir(), '.epicshop', 'cache')\n\nexport const fsCache: CachifiedCache = {\n\tname: 'Filesystem cache',\n\tasync get(key) {\n\t\ttry {\n\t\t\tconst filePath = path.join(cacheDir, md5(key))\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\t\t\tconst data = await fsExtra.readJSON(filePath)\n\t\t\treturn data\n\t\t} catch (error: unknown) {\n\t\t\tif (\n\t\t\t\terror instanceof Error &&\n\t\t\t\t'code' in error &&\n\t\t\t\terror.code === 'ENOENT'\n\t\t\t) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tthrow error\n\t\t}\n\t},\n\tasync set(key, entry) {\n\t\tconst filePath = path.join(cacheDir, md5(key))\n\t\tawait fsExtra.ensureDir(path.dirname(filePath))\n\t\tawait fsExtra.writeJSON(filePath, entry)\n\t},\n\tasync delete(key) {\n\t\tconst filePath = path.join(cacheDir, md5(key))\n\t\tawait fsExtra.remove(filePath)\n\t},\n}\n\nexport async function deleteCache() {\n\tif (process.env.EPICSHOP_DEPLOYED) return null\n\n\ttry {\n\t\tif (await fsExtra.exists(cacheDir)) {\n\t\t\tawait fsExtra.remove(cacheDir)\n\t\t}\n\t} catch (error) {\n\t\tconsole.error(`Error deleting the cache in ${cacheDir}`, error)\n\t}\n}\n\nexport function makeSingletonCache<CacheEntryType>(name: string) {\n\treturn remember(name, () => {\n\t\tconst lruInstance = new LRUCache<string, CacheEntry<CacheEntryType>>({\n\t\t\tmax: 1000,\n\t\t})\n\n\t\tconst lru = {\n\t\t\tname,\n\t\t\tset: (key, value) => {\n\t\t\t\tconst ttl = C.totalTtl(value.metadata)\n\t\t\t\tlruInstance.set(key, value, {\n\t\t\t\t\tttl: ttl === Infinity ? undefined : ttl,\n\t\t\t\t\tstart: value.metadata.createdTime,\n\t\t\t\t})\n\t\t\t\treturn value\n\t\t\t},\n\t\t\tget: key => lruInstance.get(key),\n\t\t\tdelete: key => lruInstance.delete(key),\n\t\t} satisfies C.Cache<CacheEntryType>\n\n\t\treturn lru\n\t})\n}\n\nexport async function cachified<Value>({\n\trequest,\n\ttimings,\n\tkey,\n\t// TODO: figure out what this was for before...\n\t// eslint-disable-next-line @typescript-eslint/no-unused-vars\n\ttimingKey = key.length > 18 ? `${key.slice(0, 7)}...${key.slice(-8)}` : key,\n\t...options\n}: Omit<C.CachifiedOptions<Value>, 'forceFresh'> & {\n\trequest?: Request\n\ttimings?: Timings\n\tforceFresh?: boolean | string\n\ttimingKey?: string\n}): Promise<Value> {\n\treturn C.cachified(\n\t\t{\n\t\t\t...options,\n\t\t\tkey,\n\t\t\tforceFresh: await shouldForceFresh({\n\t\t\t\tforceFresh: options.forceFresh,\n\t\t\t\trequest,\n\t\t\t\tkey,\n\t\t\t}),\n\t\t},\n\t\tC.mergeReporters(\n\t\t\tcachifiedTimingReporter(timings),\n\t\t\tprocess.env.EPICSHOP_DEBUG_CACHE ? verboseReporter() : undefined,\n\t\t),\n\t)\n}\n\nexport async function shouldForceFresh({\n\tforceFresh,\n\trequest,\n\tkey,\n}: {\n\tforceFresh?: boolean | string\n\trequest?: Request\n\tkey?: string\n}) {\n\tif (typeof forceFresh === 'boolean') return forceFresh\n\tif (typeof forceFresh === 'string' && key) {\n\t\treturn forceFresh.split(',').includes(key)\n\t}\n\n\tif (!request) return false\n\tconst fresh = new URL(request.url).searchParams.get('fresh')\n\tif (typeof fresh !== 'string') return false\n\tif (fresh === '') return true\n\tif (!key) return false\n\n\treturn fresh.split(',').includes(key)\n}\n"]}
@@ -0,0 +1,8 @@
1
+ import chokidar from 'chokidar';
2
+ import closeWithGrace from 'close-with-grace';
3
+ declare global {
4
+ var __change_tracker_watcher__: ReturnType<typeof chokidar.watch> | undefined, __change_tracker_close_with_grace_return__: ReturnType<typeof closeWithGrace>;
5
+ }
6
+ export declare function getWatcher(): chokidar.FSWatcher | null;
7
+ export declare function getOptionalWatcher(): chokidar.FSWatcher | undefined;
8
+ //# sourceMappingURL=change-tracker.server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"change-tracker.server.d.ts","sourceRoot":"","sources":["../../src/change-tracker.server.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,UAAU,CAAA;AAC/B,OAAO,cAAc,MAAM,kBAAkB,CAAA;AAE7C,OAAO,CAAC,MAAM,CAAC;IACd,IAAI,0BAA0B,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,CAAC,GAAG,SAAS,EAC5E,0CAA0C,EAAE,UAAU,CACrD,OAAO,cAAc,CACrB,CAAA;CACF;AAID,wBAAgB,UAAU,8BAuBzB;AAED,wBAAgB,kBAAkB,mCAEjC"}
@@ -0,0 +1,32 @@
1
+ import chokidar from 'chokidar';
2
+ import closeWithGrace from 'close-with-grace';
3
+ let watcher = global.__change_tracker_watcher__;
4
+ export function getWatcher() {
5
+ if (process.env.EPICSHOP_DEPLOYED ??
6
+ process.env.EPICSHOP_DISABLE_WATCHER === 'true') {
7
+ return null;
8
+ }
9
+ if (watcher)
10
+ return watcher;
11
+ const workshopRoot = process.env.EPICSHOP_CONTEXT_CWD ?? process.cwd();
12
+ watcher = chokidar.watch(workshopRoot, {
13
+ ignoreInitial: true,
14
+ ignored: [
15
+ '**/.git/**',
16
+ '**/node_modules/**',
17
+ '**/build/**',
18
+ '**/public/build/**',
19
+ '**/playwright-report/**',
20
+ '**/dist/**',
21
+ '**/.cache/**',
22
+ ],
23
+ });
24
+ global.__change_tracker_watcher__ = watcher;
25
+ return watcher;
26
+ }
27
+ export function getOptionalWatcher() {
28
+ return watcher;
29
+ }
30
+ global.__change_tracker_close_with_grace_return__?.uninstall();
31
+ global.__change_tracker_close_with_grace_return__ = closeWithGrace(() => watcher?.close());
32
+ //# sourceMappingURL=change-tracker.server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"change-tracker.server.js","sourceRoot":"","sources":["../../src/change-tracker.server.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,UAAU,CAAA;AAC/B,OAAO,cAAc,MAAM,kBAAkB,CAAA;AAS7C,IAAI,OAAO,GAAG,MAAM,CAAC,0BAA0B,CAAA;AAE/C,MAAM,UAAU,UAAU;IACzB,IACC,OAAO,CAAC,GAAG,CAAC,iBAAiB;QAC7B,OAAO,CAAC,GAAG,CAAC,wBAAwB,KAAK,MAAM,EAC9C,CAAC;QACF,OAAO,IAAI,CAAA;IACZ,CAAC;IACD,IAAI,OAAO;QAAE,OAAO,OAAO,CAAA;IAC3B,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,OAAO,CAAC,GAAG,EAAE,CAAA;IACtE,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,YAAY,EAAE;QACtC,aAAa,EAAE,IAAI;QACnB,OAAO,EAAE;YACR,YAAY;YACZ,oBAAoB;YACpB,aAAa;YACb,oBAAoB;YACpB,yBAAyB;YACzB,YAAY;YACZ,cAAc;SACd;KACD,CAAC,CAAA;IACF,MAAM,CAAC,0BAA0B,GAAG,OAAO,CAAA;IAC3C,OAAO,OAAO,CAAA;AACf,CAAC;AAED,MAAM,UAAU,kBAAkB;IACjC,OAAO,OAAO,CAAA;AACf,CAAC;AAED,MAAM,CAAC,0CAA0C,EAAE,SAAS,EAAE,CAAA;AAC9D,MAAM,CAAC,0CAA0C,GAAG,cAAc,CAAC,GAAG,EAAE,CACvE,OAAO,EAAE,KAAK,EAAE,CAChB,CAAA","sourcesContent":["import chokidar from 'chokidar'\nimport closeWithGrace from 'close-with-grace'\n\ndeclare global {\n\tvar __change_tracker_watcher__: ReturnType<typeof chokidar.watch> | undefined,\n\t\t__change_tracker_close_with_grace_return__: ReturnType<\n\t\t\ttypeof closeWithGrace\n\t\t>\n}\n\nlet watcher = global.__change_tracker_watcher__\n\nexport function getWatcher() {\n\tif (\n\t\tprocess.env.EPICSHOP_DEPLOYED ??\n\t\tprocess.env.EPICSHOP_DISABLE_WATCHER === 'true'\n\t) {\n\t\treturn null\n\t}\n\tif (watcher) return watcher\n\tconst workshopRoot = process.env.EPICSHOP_CONTEXT_CWD ?? process.cwd()\n\twatcher = chokidar.watch(workshopRoot, {\n\t\tignoreInitial: true,\n\t\tignored: [\n\t\t\t'**/.git/**',\n\t\t\t'**/node_modules/**',\n\t\t\t'**/build/**',\n\t\t\t'**/public/build/**',\n\t\t\t'**/playwright-report/**',\n\t\t\t'**/dist/**',\n\t\t\t'**/.cache/**',\n\t\t],\n\t})\n\tglobal.__change_tracker_watcher__ = watcher\n\treturn watcher\n}\n\nexport function getOptionalWatcher() {\n\treturn watcher\n}\n\nglobal.__change_tracker_close_with_grace_return__?.uninstall()\nglobal.__change_tracker_close_with_grace_return__ = closeWithGrace(() =>\n\twatcher?.close(),\n)\n"]}
@@ -0,0 +1,16 @@
1
+ import { type Root as MdastRoot } from 'mdast';
2
+ export type EmbeddedFile = {
3
+ error?: boolean;
4
+ file: string;
5
+ hash: string;
6
+ line?: number;
7
+ warning?: string;
8
+ };
9
+ export type CodeFileData = {
10
+ mdxFile: string;
11
+ cacheLocation: string;
12
+ cachedEmbeddedFiles: Map<string, EmbeddedFile>;
13
+ embeddedFiles: Map<string, EmbeddedFile>;
14
+ };
15
+ export declare function remarkCodeFile(data: CodeFileData): (tree: MdastRoot) => Promise<void>;
16
+ //# sourceMappingURL=codefile-mdx.server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codefile-mdx.server.d.ts","sourceRoot":"","sources":["../../src/codefile-mdx.server.ts"],"names":[],"mappings":"AAIA,OAAO,EAAoB,KAAK,IAAI,IAAI,SAAS,EAAe,MAAM,OAAO,CAAA;AAuB7E,MAAM,MAAM,YAAY,GAAG;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IAC1B,OAAO,EAAE,MAAM,CAAA;IACf,aAAa,EAAE,MAAM,CAAA;IACrB,mBAAmB,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;IAC9C,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;CACxC,CAAA;AA4LD,wBAAgB,cAAc,CAAC,IAAI,EAAE,YAAY,UA6IA,SAAS,mBAczD"}
@@ -0,0 +1,275 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { createProcessor } from '@mdx-js/mdx';
4
+ import md5 from 'md5-hex';
5
+ import { removePosition } from 'unist-util-remove-position';
6
+ import { visit } from 'unist-util-visit';
7
+ import * as z from 'zod';
8
+ const APP_TYPES = ['problem', 'solution', 'playground'];
9
+ const safePath = (s) => s.replace(/\\/g, '/');
10
+ const REG_EXP = /^(?:\d+(?:-\d+)?,)*\d+(?:-\d+)?$/;
11
+ const isValidRangeFormat = (value) => value ? REG_EXP.test(value) : true;
12
+ const transformRange = (value) => value?.split(',').map(range => {
13
+ const [start, end] = range.split('-').map(Number);
14
+ return [start, end ?? start];
15
+ });
16
+ const isRangeBounded = (range, ctx, lines) => {
17
+ if (!lines || !Array.isArray(range))
18
+ return;
19
+ if (range.flat().some(r => r < 1 || r > lines)) {
20
+ ctx.addIssue({
21
+ code: z.ZodIssueCode.custom,
22
+ message: `Range must be between 1 and ${lines}`,
23
+ });
24
+ }
25
+ };
26
+ const isRangeInOrder = (range) => Array.isArray(range)
27
+ ? range.every(([a, b]) => !isNaN(Number(a)) && !isNaN(Number(b)) && b >= a)
28
+ : true;
29
+ const isRangesNonOverlapping = (range) => {
30
+ if (!Array.isArray(range))
31
+ return true;
32
+ return range.every(([a], i) => i === 0 || (range[i - 1]?.[1] ?? 0) < a);
33
+ };
34
+ let fileContentCache = new Map();
35
+ async function getFileContent(filePath) {
36
+ if (fileContentCache.has(filePath)) {
37
+ return fileContentCache.get(filePath);
38
+ }
39
+ try {
40
+ const content = await fs.promises.readFile(filePath, 'utf-8');
41
+ const fileContent = content.split('\n');
42
+ fileContentCache.set(filePath, fileContent);
43
+ return fileContent;
44
+ }
45
+ catch (error) {
46
+ console.warn(`@epic-web/workshop-app - invalid CodeFile.\nCould not read file: ${filePath}\n`);
47
+ }
48
+ }
49
+ async function validateProps(props, appDir) {
50
+ let validRange;
51
+ let linesCount = 0;
52
+ const BooleanSchema = z
53
+ .nullable(z.string())
54
+ .optional()
55
+ .refine(v => ['true', 'false', null, undefined].includes(v), 'optional boolean key can be "true", "false", null or undefined')
56
+ .transform(v => v === null || Boolean(v));
57
+ const RangeSchema = z
58
+ .string()
59
+ .optional()
60
+ .refine(isValidRangeFormat, 'Invalid range format')
61
+ .transform(transformRange)
62
+ .superRefine((val, ctx) => isRangeBounded(val, ctx, linesCount))
63
+ .refine(isRangeInOrder, 'Range must be in order low-high');
64
+ const inputSchema = z
65
+ .object({
66
+ file: z
67
+ .string()
68
+ .nonempty()
69
+ .transform(async (file, ctx) => {
70
+ const fullPath = path.join(appDir, file);
71
+ const content = await getFileContent(fullPath);
72
+ if (!content) {
73
+ ctx.addIssue({
74
+ code: z.ZodIssueCode.custom,
75
+ message: `Could not read file`,
76
+ fatal: true,
77
+ });
78
+ return z.NEVER;
79
+ }
80
+ linesCount = content.length;
81
+ // @mdx-js/mdx parser can NOT handle relative path with backslashes
82
+ return {
83
+ fullPath: safePath(fullPath),
84
+ filePath: safePath(file),
85
+ content,
86
+ };
87
+ }),
88
+ range: RangeSchema.refine(range => {
89
+ const isValid = isRangesNonOverlapping(range);
90
+ // we use this value in highlight refine
91
+ validRange = isValid ? range : undefined;
92
+ return isValid;
93
+ }, 'Ranges must not overlap'),
94
+ highlight: RangeSchema.refine(highlight => {
95
+ if (!Array.isArray(highlight) || !Array.isArray(validRange)) {
96
+ return z.NEVER;
97
+ }
98
+ return highlight.every(([hStart, hEnd]) => validRange?.some(([rStart, rEnd]) => hStart >= rStart && hEnd <= rEnd));
99
+ }, 'Highlight range must be within defined range')
100
+ .transform(() => props.highlight)
101
+ .optional(),
102
+ nonumber: BooleanSchema,
103
+ nocopy: BooleanSchema,
104
+ buttons: z
105
+ .string()
106
+ .optional()
107
+ .transform(str => (str ? str.split(',') : []))
108
+ .refine(arr => arr.every(item => APP_TYPES.includes(item)), {
109
+ message: `Buttons can only be any of ${APP_TYPES.join(',')}`,
110
+ }),
111
+ })
112
+ .strict();
113
+ return inputSchema.safeParseAsync(props);
114
+ }
115
+ async function createErrorNotification(node, errors, mdxFile, appType) {
116
+ const filename = path.basename(mdxFile);
117
+ const startLine = node.position?.start.line;
118
+ const endLine = node.position?.end.line;
119
+ const codeFence = async () => {
120
+ if (startLine && endLine) {
121
+ const contentStr = await getFileContent(mdxFile);
122
+ const content = contentStr?.slice(startLine - 1, endLine).join('\n');
123
+ if (content) {
124
+ return `
125
+ \`\`\`tsx filename=${filename} start=${startLine} nocopy
126
+ ${content}
127
+ \`\`\``.trim();
128
+ }
129
+ }
130
+ return '';
131
+ };
132
+ const mdxSource = `
133
+ <CodeFileNotification variant="error" file="${filename}" line="${startLine}" type="${appType}">
134
+ <callout-danger class="notification">
135
+ <div className="title">CodeFile Error: invalid input</div>
136
+ ${errors.map(error => `<div>${error}</div>`).join('')}
137
+ ${await codeFence()}
138
+ </callout-danger>
139
+ </CodeFileNotification>`;
140
+ return mdxToMdast(mdxSource);
141
+ }
142
+ // based on https://github.com/sindresorhus/strip-indent
143
+ function stripIndent(string) {
144
+ const match = string.match(/^[ \t]*(?=\S)/gm);
145
+ const indent = match?.reduce((r, a) => Math.min(r, a.length), Infinity) ?? 0;
146
+ if (indent === 0) {
147
+ return string;
148
+ }
149
+ const regex = new RegExp(`^[ \\t]{${indent}}`, 'gm');
150
+ return string.replace(regex, '');
151
+ }
152
+ function mdxToMdast(mdx) {
153
+ const processor = createProcessor();
154
+ const mdast = processor.parse(mdx.trim());
155
+ removePosition(mdast, { force: true });
156
+ return mdast.type === 'root' ? mdast.children : [mdast];
157
+ }
158
+ export function remarkCodeFile(data) {
159
+ fileContentCache = new Map();
160
+ const mdxFile = data.mdxFile;
161
+ const appDir = path.dirname(mdxFile);
162
+ const appType = mdxFile.includes('problem')
163
+ ? 'problem'
164
+ : mdxFile.includes('solution')
165
+ ? 'solution'
166
+ : 'other'; // not in exercise
167
+ async function replaceCodeFileNode({ node, parent, }) {
168
+ if (!parent) {
169
+ console.warn('Unexpected error: replaceCodeFileNode called without a Parent');
170
+ return;
171
+ }
172
+ const index = parent.children.indexOf(node);
173
+ if (index === -1) {
174
+ console.warn('Unexpected error: replaceCodeFileNode could not find node index in Parent');
175
+ return;
176
+ }
177
+ const attributes = node.attributes;
178
+ const props = {};
179
+ for (const { name, value } of attributes) {
180
+ props[name] = value;
181
+ }
182
+ const result = await validateProps(props, appDir);
183
+ if (!result.success) {
184
+ const errors = result.error.issues.map(({ message, path }) => path[0] ? `${message}: ${path[0]}="${props[path[0]]}"` : message);
185
+ const notification = await createErrorNotification(node, errors, mdxFile, appType);
186
+ parent.children.splice(index, 1, ...notification);
187
+ data.embeddedFiles.set('invalid input', {
188
+ error: true,
189
+ file: props.file,
190
+ hash: '',
191
+ line: node.position?.start.line ?? 1,
192
+ });
193
+ return;
194
+ }
195
+ const { file: { content, filePath, fullPath }, highlight, range, } = result.data;
196
+ const language = path.extname(filePath).substring(1);
197
+ const meta = [`filename=${filePath}`];
198
+ // nonumbers nocopy ....
199
+ Object.entries(result.data).forEach(([key, val]) => typeof val === 'boolean' && val && meta.push(`${key}=true`));
200
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
201
+ if (result.data.buttons) {
202
+ meta.push(`buttons=${result.data.buttons.join(',')}`);
203
+ meta.push(`type=${appType}`);
204
+ meta.push(`fullpath=${fullPath}`);
205
+ // Avoid the headache of finding the separator on the client side
206
+ // path.sep is always / on client side
207
+ meta.push(`sep=${path.sep}`);
208
+ }
209
+ if (highlight?.length) {
210
+ meta.push(`lines=${highlight}`);
211
+ }
212
+ const fileSections = range?.length ? range : [[1, content.length]];
213
+ const rangesContent = [];
214
+ const preNodes = [];
215
+ for (const [start, end] of fileSections) {
216
+ const rangeContent = stripIndent(content.slice(start ? start - 1 : 0, end).join('\n'));
217
+ rangesContent.push(rangeContent);
218
+ const mdxSource = `
219
+ \`\`\`${language} ${meta.concat(`start=${start}`).join(' ')}
220
+ ${rangeContent}
221
+ \`\`\``;
222
+ preNodes.push(...mdxToMdast(mdxSource));
223
+ }
224
+ /**
225
+ * Show a warning above the file content if the range we show changed in the
226
+ * embedded file and the range in <CodeFile range="a-b"> did not change.
227
+ * The warning will be removed automatically after the <CodeFile> range changes
228
+ * or by canceling it from the UI.
229
+ */
230
+ const embeddedKey = md5(fullPath + JSON.stringify(range));
231
+ const contentHash = md5(rangesContent.join(','));
232
+ const newData = {
233
+ file: fullPath,
234
+ hash: contentHash,
235
+ };
236
+ const cachedData = data.cachedEmbeddedFiles.get(embeddedKey);
237
+ if (cachedData &&
238
+ // If a warning existed previously and its hash matched the current hash,
239
+ // then the changes were reverted and the warning will be remove
240
+ ((cachedData.warning && cachedData.warning !== contentHash) ??
241
+ (!cachedData.warning && cachedData.hash !== contentHash))) {
242
+ // keep previously saved warning or previous hash
243
+ newData.warning = cachedData.warning ?? cachedData.hash;
244
+ const startLine = node.position?.start.line ?? 1;
245
+ newData.line = startLine;
246
+ const mdxFilename = path.basename(mdxFile);
247
+ const filename = path.basename(filePath);
248
+ const warning = `
249
+ <CodeFileNotification variant="warning" file="${mdxFilename}" line="${startLine}" type="${appType}"
250
+ cacheLocation="${data.cacheLocation}" embeddedKey="${embeddedKey}">
251
+ <callout-warning class="notification">
252
+ <div className="title">CodeFile Warning:</div>
253
+ <div>file ${filename} content was changed, review 'range' and 'highlight' inputs</div>
254
+ </callout-warning>
255
+ </CodeFileNotification>`;
256
+ preNodes.unshift(...mdxToMdast(warning));
257
+ }
258
+ data.embeddedFiles.set(embeddedKey, newData);
259
+ // replace <CodeFile> with embedded file content
260
+ parent.children.splice(index, 1, ...preNodes);
261
+ }
262
+ return async function codeFileTransformer(tree) {
263
+ const codeFiles = [];
264
+ const filter = { type: 'mdxJsxFlowElement', name: 'CodeFile' };
265
+ visit(tree, filter, ((node, _index, parent) => {
266
+ codeFiles.push({ node, parent });
267
+ }));
268
+ for (const props of codeFiles) {
269
+ await replaceCodeFileNode(props);
270
+ }
271
+ // cleanup
272
+ fileContentCache = new Map();
273
+ };
274
+ }
275
+ //# sourceMappingURL=codefile-mdx.server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codefile-mdx.server.js","sourceRoot":"","sources":["../../src/codefile-mdx.server.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'node:fs'\nimport path from 'node:path'\nimport { createProcessor } from '@mdx-js/mdx'\nimport md5 from 'md5-hex'\nimport { type RootContent, type Root as MdastRoot, type Parent } from 'mdast'\nimport {\n\ttype MdxJsxAttribute,\n\ttype MdxJsxFlowElement,\n} from 'mdast-util-mdx-jsx'\nimport { removePosition } from 'unist-util-remove-position'\nimport { type Visitor, visit } from 'unist-util-visit'\nimport * as z from 'zod'\n\ntype CodeFile = {\n\tnode: MdxJsxFlowElement\n\tparent: Parent | null | undefined\n}\n\ntype RangeArray = [number, number][] | undefined\n\ntype CodeFileProps = Record<string, unknown>\n\ntype PathContentMap = Map<string, string[]>\n\nconst APP_TYPES = ['problem', 'solution', 'playground'] as const\ntype AppTypes = typeof APP_TYPES\n\nexport type EmbeddedFile = {\n\terror?: boolean\n\tfile: string\n\thash: string\n\tline?: number\n\twarning?: string\n}\n\nexport type CodeFileData = {\n\tmdxFile: string\n\tcacheLocation: string\n\tcachedEmbeddedFiles: Map<string, EmbeddedFile>\n\tembeddedFiles: Map<string, EmbeddedFile>\n}\n\nconst safePath = (s: string) => s.replace(/\\\\/g, '/')\n\nconst REG_EXP = /^(?:\\d+(?:-\\d+)?,)*\\d+(?:-\\d+)?$/\n\nconst isValidRangeFormat = (value: string | undefined) =>\n\tvalue ? REG_EXP.test(value) : true\n\nconst transformRange = (value: string | undefined) =>\n\tvalue?.split(',').map(range => {\n\t\tconst [start, end] = range.split('-').map(Number)\n\t\treturn [start, end ?? start] as [number, number]\n\t})\n\nconst isRangeBounded = (\n\trange: RangeArray,\n\tctx: z.RefinementCtx,\n\tlines: number,\n) => {\n\tif (!lines || !Array.isArray(range)) return\n\tif (range.flat().some(r => r < 1 || r > lines)) {\n\t\tctx.addIssue({\n\t\t\tcode: z.ZodIssueCode.custom,\n\t\t\tmessage: `Range must be between 1 and ${lines}`,\n\t\t})\n\t}\n}\n\nconst isRangeInOrder = (range: RangeArray) =>\n\tArray.isArray(range)\n\t\t? range.every(([a, b]) => !isNaN(Number(a)) && !isNaN(Number(b)) && b >= a)\n\t\t: true\n\nconst isRangesNonOverlapping = (range: RangeArray) => {\n\tif (!Array.isArray(range)) return true\n\treturn range.every(([a], i) => i === 0 || (range[i - 1]?.[1] ?? 0) < a)\n}\n\nlet fileContentCache: PathContentMap = new Map()\nasync function getFileContent(filePath: string) {\n\tif (fileContentCache.has(filePath)) {\n\t\treturn fileContentCache.get(filePath)\n\t}\n\ttry {\n\t\tconst content = await fs.promises.readFile(filePath, 'utf-8')\n\t\tconst fileContent = content.split('\\n')\n\t\tfileContentCache.set(filePath, fileContent)\n\t\treturn fileContent\n\t} catch (error) {\n\t\tconsole.warn(\n\t\t\t`@epic-web/workshop-app - invalid CodeFile.\\nCould not read file: ${filePath}\\n`,\n\t\t)\n\t}\n}\n\nasync function validateProps(props: CodeFileProps, appDir: string) {\n\tlet validRange: RangeArray\n\tlet linesCount = 0\n\n\tconst BooleanSchema = z\n\t\t.nullable(z.string())\n\t\t.optional()\n\t\t.refine(\n\t\t\tv => ['true', 'false', null, undefined].includes(v),\n\t\t\t'optional boolean key can be \"true\", \"false\", null or undefined',\n\t\t)\n\t\t.transform(v => v === null || Boolean(v))\n\n\tconst RangeSchema = z\n\t\t.string()\n\t\t.optional()\n\t\t.refine(isValidRangeFormat, 'Invalid range format')\n\t\t.transform(transformRange)\n\t\t.superRefine((val, ctx) => isRangeBounded(val, ctx, linesCount))\n\t\t.refine(isRangeInOrder, 'Range must be in order low-high')\n\n\tconst inputSchema = z\n\t\t.object({\n\t\t\tfile: z\n\t\t\t\t.string()\n\t\t\t\t.nonempty()\n\t\t\t\t.transform(async (file, ctx) => {\n\t\t\t\t\tconst fullPath = path.join(appDir, file)\n\t\t\t\t\tconst content = await getFileContent(fullPath)\n\t\t\t\t\tif (!content) {\n\t\t\t\t\t\tctx.addIssue({\n\t\t\t\t\t\t\tcode: z.ZodIssueCode.custom,\n\t\t\t\t\t\t\tmessage: `Could not read file`,\n\t\t\t\t\t\t\tfatal: true,\n\t\t\t\t\t\t})\n\t\t\t\t\t\treturn z.NEVER\n\t\t\t\t\t}\n\t\t\t\t\tlinesCount = content.length\n\t\t\t\t\t// @mdx-js/mdx parser can NOT handle relative path with backslashes\n\t\t\t\t\treturn {\n\t\t\t\t\t\tfullPath: safePath(fullPath),\n\t\t\t\t\t\tfilePath: safePath(file),\n\t\t\t\t\t\tcontent,\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\trange: RangeSchema.refine(range => {\n\t\t\t\tconst isValid = isRangesNonOverlapping(range)\n\t\t\t\t// we use this value in highlight refine\n\t\t\t\tvalidRange = isValid ? range : undefined\n\t\t\t\treturn isValid\n\t\t\t}, 'Ranges must not overlap'),\n\t\t\thighlight: RangeSchema.refine(highlight => {\n\t\t\t\tif (!Array.isArray(highlight) || !Array.isArray(validRange)) {\n\t\t\t\t\treturn z.NEVER\n\t\t\t\t}\n\t\t\t\treturn highlight.every(([hStart, hEnd]) =>\n\t\t\t\t\tvalidRange?.some(\n\t\t\t\t\t\t([rStart, rEnd]) => hStart >= rStart && hEnd <= rEnd,\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}, 'Highlight range must be within defined range')\n\t\t\t\t.transform(() => props.highlight as Array<string>)\n\t\t\t\t.optional(),\n\t\t\tnonumber: BooleanSchema,\n\t\t\tnocopy: BooleanSchema,\n\t\t\tbuttons: z\n\t\t\t\t.string()\n\t\t\t\t.optional()\n\t\t\t\t.transform(str => (str ? (str.split(',') as unknown as AppTypes) : []))\n\t\t\t\t.refine(arr => arr.every(item => APP_TYPES.includes(item)), {\n\t\t\t\t\tmessage: `Buttons can only be any of ${APP_TYPES.join(',')}`,\n\t\t\t\t}),\n\t\t})\n\t\t.strict()\n\n\treturn inputSchema.safeParseAsync(props)\n}\n\nasync function createErrorNotification(\n\tnode: MdxJsxFlowElement,\n\terrors: string[],\n\tmdxFile: string,\n\tappType: string,\n) {\n\tconst filename = path.basename(mdxFile)\n\tconst startLine = node.position?.start.line\n\tconst endLine = node.position?.end.line\n\n\tconst codeFence = async () => {\n\t\tif (startLine && endLine) {\n\t\t\tconst contentStr = await getFileContent(mdxFile)\n\t\t\tconst content = contentStr?.slice(startLine - 1, endLine).join('\\n')\n\t\t\tif (content) {\n\t\t\t\treturn `\n\\`\\`\\`tsx filename=${filename} start=${startLine} nocopy\n${content}\n\\`\\`\\``.trim()\n\t\t\t}\n\t\t}\n\t\treturn ''\n\t}\n\n\tconst mdxSource = `\n<CodeFileNotification variant=\"error\" file=\"${filename}\" line=\"${startLine}\" type=\"${appType}\">\n <callout-danger class=\"notification\">\n <div className=\"title\">CodeFile Error: invalid input</div>\n ${errors.map(error => `<div>${error}</div>`).join('')}\n${await codeFence()}\n </callout-danger>\n</CodeFileNotification>`\n\n\treturn mdxToMdast(mdxSource)\n}\n\n// based on https://github.com/sindresorhus/strip-indent\nfunction stripIndent(string: string) {\n\tconst match = string.match(/^[ \\t]*(?=\\S)/gm)\n\tconst indent = match?.reduce((r, a) => Math.min(r, a.length), Infinity) ?? 0\n\tif (indent === 0) {\n\t\treturn string\n\t}\n\tconst regex = new RegExp(`^[ \\\\t]{${indent}}`, 'gm')\n\treturn string.replace(regex, '')\n}\n\nfunction mdxToMdast(mdx: string) {\n\tconst processor = createProcessor()\n\tconst mdast = processor.parse(mdx.trim()) as MdastRoot | RootContent\n\tremovePosition(mdast, { force: true })\n\treturn mdast.type === 'root' ? mdast.children : [mdast]\n}\n\nexport function remarkCodeFile(data: CodeFileData) {\n\tfileContentCache = new Map()\n\tconst mdxFile = data.mdxFile\n\tconst appDir = path.dirname(mdxFile)\n\tconst appType = mdxFile.includes('problem')\n\t\t? 'problem'\n\t\t: mdxFile.includes('solution')\n\t\t\t? 'solution'\n\t\t\t: 'other' // not in exercise\n\n\tasync function replaceCodeFileNode({\n\t\tnode,\n\t\tparent,\n\t}: CodeFile): Promise<void> {\n\t\tif (!parent) {\n\t\t\tconsole.warn(\n\t\t\t\t'Unexpected error: replaceCodeFileNode called without a Parent',\n\t\t\t)\n\t\t\treturn\n\t\t}\n\t\tconst index = parent.children.indexOf(node)\n\t\tif (index === -1) {\n\t\t\tconsole.warn(\n\t\t\t\t'Unexpected error: replaceCodeFileNode could not find node index in Parent',\n\t\t\t)\n\t\t\treturn\n\t\t}\n\t\tconst attributes = node.attributes as MdxJsxAttribute[]\n\t\tconst props: CodeFileProps = {}\n\t\tfor (const { name, value } of attributes) {\n\t\t\tprops[name] = value\n\t\t}\n\n\t\tconst result = await validateProps(props, appDir)\n\t\tif (!result.success) {\n\t\t\tconst errors = result.error.issues.map(({ message, path }) =>\n\t\t\t\tpath[0] ? `${message}: ${path[0]}=\"${props[path[0]]}\"` : message,\n\t\t\t)\n\t\t\tconst notification = await createErrorNotification(\n\t\t\t\tnode,\n\t\t\t\terrors,\n\t\t\t\tmdxFile,\n\t\t\t\tappType,\n\t\t\t)\n\t\t\tparent.children.splice(index, 1, ...notification)\n\n\t\t\tdata.embeddedFiles.set('invalid input', {\n\t\t\t\terror: true,\n\t\t\t\tfile: props.file as string,\n\t\t\t\thash: '',\n\t\t\t\tline: node.position?.start.line ?? 1,\n\t\t\t})\n\t\t\treturn\n\t\t}\n\n\t\tconst {\n\t\t\tfile: { content, filePath, fullPath },\n\t\t\thighlight,\n\t\t\trange,\n\t\t} = result.data\n\t\tconst language = path.extname(filePath).substring(1)\n\t\tconst meta = [`filename=${filePath}`]\n\n\t\t// nonumbers nocopy ....\n\t\tObject.entries(result.data).forEach(\n\t\t\t([key, val]) =>\n\t\t\t\ttypeof val === 'boolean' && val && meta.push(`${key}=true`),\n\t\t)\n\n\t\t// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n\t\tif (result.data.buttons) {\n\t\t\tmeta.push(`buttons=${result.data.buttons.join(',')}`)\n\t\t\tmeta.push(`type=${appType}`)\n\t\t\tmeta.push(`fullpath=${fullPath}`)\n\t\t\t// Avoid the headache of finding the separator on the client side\n\t\t\t// path.sep is always / on client side\n\t\t\tmeta.push(`sep=${path.sep}`)\n\t\t}\n\n\t\tif (highlight?.length) {\n\t\t\tmeta.push(`lines=${highlight}`)\n\t\t}\n\n\t\tconst fileSections = range?.length ? range : [[1, content.length]]\n\t\tconst rangesContent = []\n\t\tconst preNodes = []\n\t\tfor (const [start, end] of fileSections) {\n\t\t\tconst rangeContent = stripIndent(\n\t\t\t\tcontent.slice(start ? start - 1 : 0, end).join('\\n'),\n\t\t\t)\n\t\t\trangesContent.push(rangeContent)\n\t\t\tconst mdxSource = `\n\\`\\`\\`${language} ${meta.concat(`start=${start}`).join(' ')}\n${rangeContent}\n\\`\\`\\``\n\t\t\tpreNodes.push(...mdxToMdast(mdxSource))\n\t\t}\n\n\t\t/**\n\t\t * Show a warning above the file content if the range we show changed in the\n\t\t * embedded file and the range in <CodeFile range=\"a-b\"> did not change.\n\t\t * The warning will be removed automatically after the <CodeFile> range changes\n\t\t * or by canceling it from the UI.\n\t\t */\n\t\tconst embeddedKey = md5(fullPath + JSON.stringify(range))\n\t\tconst contentHash = md5(rangesContent.join(','))\n\t\tconst newData: EmbeddedFile = {\n\t\t\tfile: fullPath,\n\t\t\thash: contentHash,\n\t\t}\n\t\tconst cachedData = data.cachedEmbeddedFiles.get(embeddedKey)\n\n\t\tif (\n\t\t\tcachedData &&\n\t\t\t// If a warning existed previously and its hash matched the current hash,\n\t\t\t// then the changes were reverted and the warning will be remove\n\t\t\t((cachedData.warning && cachedData.warning !== contentHash) ??\n\t\t\t\t(!cachedData.warning && cachedData.hash !== contentHash))\n\t\t) {\n\t\t\t// keep previously saved warning or previous hash\n\t\t\tnewData.warning = cachedData.warning ?? cachedData.hash\n\t\t\tconst startLine = node.position?.start.line ?? 1\n\t\t\tnewData.line = startLine\n\t\t\tconst mdxFilename = path.basename(mdxFile)\n\t\t\tconst filename = path.basename(filePath)\n\t\t\tconst warning = `\n<CodeFileNotification variant=\"warning\" file=\"${mdxFilename}\" line=\"${startLine}\" type=\"${appType}\"\ncacheLocation=\"${data.cacheLocation}\" embeddedKey=\"${embeddedKey}\">\n <callout-warning class=\"notification\">\n <div className=\"title\">CodeFile Warning:</div>\n <div>file ${filename} content was changed, review 'range' and 'highlight' inputs</div>\n </callout-warning>\n</CodeFileNotification>`\n\t\t\tpreNodes.unshift(...mdxToMdast(warning))\n\t\t}\n\t\tdata.embeddedFiles.set(embeddedKey, newData)\n\n\t\t// replace <CodeFile> with embedded file content\n\t\tparent.children.splice(index, 1, ...preNodes)\n\t}\n\n\treturn async function codeFileTransformer(tree: MdastRoot) {\n\t\tconst codeFiles: CodeFile[] = []\n\t\tconst filter = { type: 'mdxJsxFlowElement', name: 'CodeFile' } as const\n\t\tvisit<MdastRoot, typeof filter>(tree, filter, ((node, _index, parent) => {\n\t\t\tcodeFiles.push({ node, parent })\n\t\t}) as Visitor<MdxJsxFlowElement, Parent>)\n\n\t\tfor (const props of codeFiles) {\n\t\t\tawait replaceCodeFileNode(props)\n\t\t}\n\n\t\t// cleanup\n\t\tfileContentCache = new Map()\n\t}\n}\n"]}
@@ -0,0 +1,11 @@
1
+ export declare function compileMdx(file: string, { request, forceFresh }?: {
2
+ request?: Request;
3
+ forceFresh?: boolean;
4
+ }): Promise<{
5
+ code: string;
6
+ title: string | null;
7
+ epicVideoEmbeds: Array<string>;
8
+ }>;
9
+ export declare function compileMarkdownString(markdownString: string): Promise<string>;
10
+ export declare function isEmbeddedFile(filePath: string): Promise<boolean>;
11
+ //# sourceMappingURL=compile-mdx.server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compile-mdx.server.d.ts","sourceRoot":"","sources":["../../src/compile-mdx.server.ts"],"names":[],"mappings":"AAkHA,wBAAsB,UAAU,CAC/B,IAAI,EAAE,MAAM,EACZ,EAAE,OAAO,EAAE,UAAU,EAAE,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAO,GACvE,OAAO,CAAC;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;CAC9B,CAAC,CA8HD;AAED,wBAAsB,qBAAqB,CAAC,cAAc,EAAE,MAAM,mBAuCjE;AAoFD,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,oBAQpD"}