repl-sdk 0.0.0 → 1.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 (61) hide show
  1. package/dist/assets/tar-worker-kdkltuRC.js +598 -0
  2. package/dist/assets/tar-worker-kdkltuRC.js.map +1 -0
  3. package/dist/codemirror-D4aIVflZ.js +110 -0
  4. package/dist/codemirror-D4aIVflZ.js.map +1 -0
  5. package/dist/gjs-CzFzkEFv.js +173 -0
  6. package/dist/gjs-CzFzkEFv.js.map +1 -0
  7. package/dist/gmd-D9OXs2v3.js +166 -0
  8. package/dist/gmd-D9OXs2v3.js.map +1 -0
  9. package/dist/hbs-CuhWjffM.js +62 -0
  10. package/dist/hbs-CuhWjffM.js.map +1 -0
  11. package/dist/index-CUWCqMoD.js +2133 -0
  12. package/dist/index-CUWCqMoD.js.map +1 -0
  13. package/dist/index.js +4 -104
  14. package/dist/index.js.map +1 -1
  15. package/dist/parse-aBKk9rfS.js +328 -0
  16. package/dist/parse-aBKk9rfS.js.map +1 -0
  17. package/dist/render-app-island-B-i8rvGi.js +61 -0
  18. package/dist/render-app-island-B-i8rvGi.js.map +1 -0
  19. package/package.json +82 -9
  20. package/src/cache.js +138 -0
  21. package/src/cdn.js +93 -0
  22. package/src/codemirror.js +161 -0
  23. package/src/compilers/ember/gjs.js +212 -0
  24. package/src/compilers/ember/gmd.js +190 -0
  25. package/src/compilers/ember/hbs.js +98 -0
  26. package/src/compilers/ember/render-app-island.js +83 -0
  27. package/src/compilers/ember.js +166 -0
  28. package/src/compilers/js.js +32 -0
  29. package/src/compilers/markdown/build-compiler.js +151 -0
  30. package/src/compilers/markdown/const.js +2 -0
  31. package/src/compilers/markdown/heading-id.js +75 -0
  32. package/src/compilers/markdown/live-code-extraction.js +198 -0
  33. package/src/compilers/markdown/parse.js +22 -0
  34. package/src/compilers/markdown/parse.test.ts +363 -0
  35. package/src/compilers/markdown/sanitize-for-glimmer.js +26 -0
  36. package/src/compilers/markdown/types.ts +21 -0
  37. package/src/compilers/markdown/utils.js +78 -0
  38. package/src/compilers/markdown.js +125 -0
  39. package/src/compilers/mermaid.js +35 -0
  40. package/src/compilers/react.js +47 -0
  41. package/src/compilers/svelte.js +116 -0
  42. package/src/compilers/vue.js +58 -0
  43. package/src/compilers.js +108 -0
  44. package/src/es-module-shim.js +53 -0
  45. package/src/index.d.ts +53 -4
  46. package/src/index.js +744 -89
  47. package/src/npm.js +58 -0
  48. package/src/request.Request.test.ts +59 -0
  49. package/src/request.js +140 -0
  50. package/src/resolve.fromImports.test.ts +35 -0
  51. package/src/resolve.fromInternalImport.test.ts +69 -0
  52. package/src/resolve.js +352 -0
  53. package/src/resolve.resolvePath.test.ts +24 -0
  54. package/src/resolve.test.ts +23 -0
  55. package/src/specifier.js +71 -0
  56. package/src/specifier.test.ts +90 -0
  57. package/src/tar-worker.js +61 -0
  58. package/src/tar.js +76 -0
  59. package/src/types.ts +335 -58
  60. package/src/utils.js +28 -1
  61. package/declarations/index.d.ts +0 -73
@@ -0,0 +1,151 @@
1
+ /**
2
+ * @typedef {import('unified').Plugin} UPlugin
3
+ */
4
+ import rehypeRaw from 'rehype-raw';
5
+ import rehypeStringify from 'rehype-stringify';
6
+ import remarkGfm from 'remark-gfm';
7
+ import remarkParse from 'remark-parse';
8
+ import remarkRehype from 'remark-rehype';
9
+ import { unified } from 'unified';
10
+ import { visit } from 'unist-util-visit';
11
+
12
+ import { GLIMDOWN_PREVIEW, GLIMDOWN_RENDER } from './const.js';
13
+ import { headingId } from './heading-id.js';
14
+ import { liveCodeExtraction } from './live-code-extraction.js';
15
+ import { sanitizeForGlimmer } from './sanitize-for-glimmer.js';
16
+
17
+ /**
18
+ * @param {import('./types').InternalOptions} options
19
+ *
20
+ * @returns {import('unified').Processor<import('hast').Root>}
21
+ */
22
+ export function buildCompiler(options) {
23
+ let compiler = unified().use(remarkParse).use(remarkGfm, { singleTilde: true }).use(headingId);
24
+
25
+ /**
26
+ * If this were "use"d after `remarkRehype`,
27
+ * remark is gone, and folks would need to work with rehype trees
28
+ */
29
+ if (options.remarkPlugins) {
30
+ options.remarkPlugins.forEach((plugin) => {
31
+ // Arrays are how plugins are passed options (for some reason?)
32
+ // why not just invoke the the function?
33
+ if (Array.isArray(plugin)) {
34
+ // @ts-ignore - unified processor types are complex and change as plugins are added
35
+ compiler = compiler.use(plugin[0], ...plugin.slice(1));
36
+ } else {
37
+ // @ts-ignore - unified processor types are complex and change as plugins are added
38
+ compiler = compiler.use(plugin);
39
+ }
40
+ });
41
+ }
42
+
43
+ // TODO: we only want to do this when we have pre > code.
44
+ // code can exist inline.
45
+ // @ts-ignore - unified processor types are complex and change as plugins are added
46
+ compiler = compiler.use(liveCodeExtraction, {
47
+ code: {
48
+ classList: ['repl-sdk__snippet'],
49
+ ...options.code,
50
+ },
51
+ demo: {
52
+ classList: ['repl-sdk__demo'],
53
+ ...options.code,
54
+ },
55
+ isLive: options.isLive,
56
+ isPreview: options.isPreview,
57
+ isBelow: options.isBelow,
58
+ needsLive: options.needsLive,
59
+ ALLOWED_FORMATS: options.ALLOWED_FORMATS,
60
+ getFlavorFromMeta: options.getFlavorFromMeta,
61
+ });
62
+
63
+ // .use(() => (tree) => visit(tree, (node) => console.log('i', node)))
64
+ // remark rehype is needed to convert markdown to HTML
65
+ // However, it also changes all the nodes, so we need another pass
66
+ // to make sure our Glimmer-aware nodes are in tact
67
+ // @ts-ignore - unified processor types are complex and change as plugins are added
68
+ compiler = compiler.use(remarkRehype, { allowDangerousHtml: true });
69
+
70
+ // Convert invocables to raw format, so Glimmer can invoke them
71
+ // @ts-ignore - unified processor types are complex and change as plugins are added
72
+ compiler = compiler.use(() => (/** @type {unknown} */ tree) => {
73
+ visit(/** @type {import('hast').Root} */ (tree), function (node) {
74
+ // We rely on an implicit transformation of data.hProperties => properties
75
+ const nodeObj = /** @type {Record<string, unknown>} */ (/** @type {unknown} */ (node));
76
+ const properties = /** @type {Record<string, unknown>} */ (
77
+ typeof node === 'object' && node !== null && 'properties' in node ? node.properties : {}
78
+ );
79
+
80
+ if (properties?.[/** @type {string} */ (/** @type {unknown} */ (GLIMDOWN_PREVIEW))]) {
81
+ return 'skip';
82
+ }
83
+
84
+ if (nodeObj.type === 'element' || ('tagName' in nodeObj && nodeObj.tagName === 'code')) {
85
+ if (properties?.[/** @type {string} */ (/** @type {unknown} */ (GLIMDOWN_RENDER))]) {
86
+ nodeObj.type = 'glimmer_raw';
87
+
88
+ return;
89
+ }
90
+
91
+ return 'skip';
92
+ }
93
+
94
+ if (nodeObj.type === 'text' || nodeObj.type === 'raw') {
95
+ // definitively not the better way, but this is supposed to detect "glimmer" nodes
96
+ if (
97
+ 'value' in nodeObj &&
98
+ typeof nodeObj.value === 'string' &&
99
+ nodeObj.value.match(/<\/?[_A-Z:0-9].*>/g)
100
+ ) {
101
+ nodeObj.type = 'glimmer_raw';
102
+ }
103
+
104
+ nodeObj.type = 'glimmer_raw';
105
+
106
+ return 'skip';
107
+ }
108
+
109
+ return;
110
+ });
111
+ });
112
+
113
+ if (options.rehypePlugins) {
114
+ options.rehypePlugins.forEach((plugin) => {
115
+ // Arrays are how plugins are passed options (for some reason?)
116
+ // why not just invoke the the function?
117
+ if (Array.isArray(plugin)) {
118
+ // @ts-ignore - unified processor types are complex and change as plugins are added
119
+ compiler = compiler.use(plugin[0], ...plugin.slice(1));
120
+ } else {
121
+ // @ts-ignore - unified processor types are complex and change as plugins are added
122
+ compiler = compiler.use(plugin);
123
+ }
124
+ });
125
+ }
126
+
127
+ // @ts-ignore - unified processor types are complex and change as plugins are added
128
+ compiler = compiler
129
+ .use(rehypeRaw, { passThrough: ['glimmer_raw', 'raw'] })
130
+ .use(() => (/** @type {unknown} */ tree) => {
131
+ visit(/** @type {import('hast').Root} */ (tree), 'glimmer_raw', (node) => {
132
+ /** @type {Record<string, unknown>} */ (node).type = 'raw';
133
+ });
134
+ });
135
+
136
+ // @ts-ignore - unified processor types are complex and change as plugins are added
137
+ compiler = compiler.use(sanitizeForGlimmer);
138
+
139
+ // Finally convert to string! oofta!
140
+ // @ts-ignore - unified processor types are complex and change as plugins are added
141
+ compiler = compiler.use(rehypeStringify, {
142
+ collapseEmptyAttributes: true,
143
+ closeSelfClosing: true,
144
+ allowParseErrors: true,
145
+ allowDangerousCharacters: true,
146
+ allowDangerousHtml: true,
147
+ });
148
+
149
+ // @ts-ignore
150
+ return compiler;
151
+ }
@@ -0,0 +1,2 @@
1
+ export const GLIMDOWN_PREVIEW = Symbol('__GLIMDOWN_PREVIEW__');
2
+ export const GLIMDOWN_RENDER = Symbol('__GLIMDOWN_RENDER__');
@@ -0,0 +1,75 @@
1
+ import { kebabCase } from 'change-case';
2
+ import { visit } from 'unist-util-visit';
3
+
4
+ /**
5
+ * @param {import('mdast').PhrasingContent[]} children
6
+ * @return {string}
7
+ */
8
+ function getDefaultId(children) {
9
+ return formatDefaultId(extractText(children));
10
+ }
11
+
12
+ /**
13
+ * @param {import('mdast').PhrasingContent[]} children
14
+ * @return {string}
15
+ */
16
+ function extractText(children) {
17
+ return children
18
+ .map(
19
+ /**
20
+ * @param {any} child
21
+ */
22
+ (child) => {
23
+ const isEmpty = !child.value?.trim();
24
+
25
+ if (!isEmpty) {
26
+ return child.value;
27
+ } else if (child.children && child.children.length > 0) {
28
+ return extractText(child.children);
29
+ } else {
30
+ return '';
31
+ }
32
+ }
33
+ )
34
+ .join(' ');
35
+ }
36
+
37
+ /**
38
+ * @param {string} value
39
+ */
40
+ function formatDefaultId(value) {
41
+ return kebabCase(value.replaceAll(/\\s+/g, ' ').trim());
42
+ }
43
+
44
+ /**
45
+ * @param {import('mdast').Heading} node
46
+ * @param {string} id
47
+ */
48
+ function setNodeId(node, id) {
49
+ if (!node.data) node.data = {};
50
+ if (!node.data.hProperties) node.data.hProperties = {};
51
+
52
+ /** @type {any} */ (node.data).id = node.data.hProperties.id = id;
53
+ }
54
+
55
+ export function headingId(options = { defaults: false }) {
56
+ /**
57
+ * @param {import('mdast').Root} node
58
+ */
59
+ return function (node) {
60
+ visit(node, 'heading', (node) => {
61
+ const lastChild = node.children[node.children.length - 1];
62
+
63
+ if (lastChild && lastChild.type === 'text') {
64
+ const string = lastChild.value.replace(/ +$/, '');
65
+ const matched = string.match(/ {#([^]+?)}$/);
66
+
67
+ if (matched) {
68
+ return;
69
+ }
70
+ }
71
+
72
+ setNodeId(node, getDefaultId(node.children));
73
+ });
74
+ };
75
+ }
@@ -0,0 +1,198 @@
1
+ import { visit } from 'unist-util-visit';
2
+
3
+ import { nextId } from '../../utils.js';
4
+ import { GLIMDOWN_PREVIEW, GLIMDOWN_RENDER } from './const.js';
5
+
6
+ /**
7
+ * Swaps live codeblocks with placeholders that the compiler can then
8
+ * use to insert compiled-from-other-sources' code into those placeholders.
9
+ *
10
+ * @type {import('unified').Plugin<[
11
+ * {
12
+ * demo: {
13
+ * classList: string[]
14
+ * },
15
+ * code: {
16
+ * classList: string[]
17
+ * },
18
+ * isLive: (meta: string, lang: string) => boolean,
19
+ * ALLOWED_FORMATS: string[],
20
+ * isPreview: (meta: string) => boolean,
21
+ * isBelow: (meta: string) => boolean,
22
+ * needsLive: (lang: string) => boolean
23
+ * getFlavorFromMeta: (meta: string, lang: string) => string | undefined
24
+ * },
25
+ * ], import('mdast').Root>}
26
+ */
27
+ export function liveCodeExtraction(options) {
28
+ const { code, demo, isLive, ALLOWED_FORMATS, isPreview, isBelow, needsLive, getFlavorFromMeta } =
29
+ options;
30
+ let { classList: snippetClasses } = code || {};
31
+ let { classList: demoClasses } = demo || {};
32
+
33
+ snippetClasses ??= [];
34
+ demoClasses ??= [];
35
+
36
+ /**
37
+ * @param {import('mdast').Code} node
38
+ */
39
+ function isRelevantCode(node) {
40
+ if (node.type !== 'code') return false;
41
+
42
+ let { meta, lang } = node;
43
+
44
+ if (!lang) {
45
+ return false;
46
+ }
47
+
48
+ meta = meta?.trim() ?? '';
49
+
50
+ if (!isLive(meta, lang)) {
51
+ return false;
52
+ }
53
+
54
+ if (!ALLOWED_FORMATS.includes(lang)) {
55
+ return false;
56
+ }
57
+
58
+ return true;
59
+ }
60
+
61
+ /**
62
+ * @param {import('mdast').Code} code
63
+ * @param {string[]} [classes]
64
+ */
65
+ function enhance(code, classes = []) {
66
+ code.data ??= {};
67
+ code.data['hProperties'] ??= {};
68
+ // This is secret-to-us-only API, so we don't really care about the type
69
+ code.data['hProperties'][/** @type {string} */ (/** @type {unknown} */ (GLIMDOWN_PREVIEW))] =
70
+ true;
71
+
72
+ return {
73
+ data: {
74
+ hProperties: { className: classes, 'data-repl-output': true },
75
+ },
76
+ type: 'div',
77
+ hProperties: { className: classes },
78
+ children: [code],
79
+ };
80
+ }
81
+
82
+ /**
83
+ * @template T
84
+ * @param {T[]} array
85
+ * @param {number} index
86
+ * @param {T[]} replacement
87
+ */
88
+ function flatReplaceAt(array, index, replacement) {
89
+ array.splice(index, 1, ...replacement);
90
+ }
91
+
92
+ // because we mutate the tree as we iterate,
93
+ // we need to make sure we don't loop forever
94
+ const seen = new Set();
95
+
96
+ return function transformer(tree, file) {
97
+ visit(tree, ['code'], function (node, index, parent) {
98
+ if (parent === null || parent === undefined) return;
99
+ if (index === null || index === undefined) return;
100
+ if (node.type !== 'code') return;
101
+
102
+ /** @type {import('mdast').Code} */
103
+ const codeNode = node;
104
+
105
+ const isRelevant = isRelevantCode(codeNode);
106
+
107
+ if (!isRelevant) {
108
+ const enhanced = enhance(codeNode, snippetClasses);
109
+
110
+ /** @type {unknown[]} */ (parent.children)[index] = /** @type {unknown} */ (enhanced);
111
+
112
+ return 'skip';
113
+ }
114
+
115
+ if (seen.has(codeNode)) {
116
+ return 'skip';
117
+ }
118
+
119
+ seen.add(codeNode);
120
+
121
+ const { meta, lang, value } = codeNode;
122
+
123
+ if (!lang) {
124
+ return 'skip';
125
+ }
126
+
127
+ /**
128
+ * Sometimes, meta is not required,
129
+ * like with the `mermaid` language
130
+ *
131
+ * NOTE: that if a flavor is required, meta is present
132
+ * if the flavor is provided
133
+ */
134
+ if (!meta) {
135
+ if (needsLive(lang)) {
136
+ return 'skip';
137
+ }
138
+ }
139
+
140
+ file.data.liveCode ??= [];
141
+
142
+ const code = value.trim();
143
+ const id = nextId();
144
+
145
+ const invokeNode = /** @type {import('mdast').Html} */ ({
146
+ type: 'html',
147
+ data: {
148
+ hProperties: {
149
+ [/** @type {string} */ (/** @type {unknown} */ (GLIMDOWN_RENDER))]: true,
150
+ },
151
+ },
152
+ value: `<div id="${id}" class="${demoClasses}"></div>`,
153
+ });
154
+
155
+ const wrapper = enhance(codeNode, snippetClasses);
156
+
157
+ /** @type {unknown[]} */ (file.data.liveCode).push({
158
+ format: lang,
159
+ flavor: getFlavorFromMeta(meta ?? '', lang),
160
+ code,
161
+ placeholderId: id,
162
+ meta,
163
+ });
164
+
165
+ const live = isLive(meta || '', lang);
166
+ const preview = isPreview(meta || '');
167
+ const below = isBelow(meta || '');
168
+
169
+ if (live && preview && below) {
170
+ flatReplaceAt(/** @type {unknown[]} */ (parent.children), index, [
171
+ /** @type {unknown} */ (wrapper),
172
+ /** @type {unknown} */ (invokeNode),
173
+ ]);
174
+
175
+ return 'skip';
176
+ }
177
+
178
+ if (live && preview) {
179
+ flatReplaceAt(/** @type {unknown[]} */ (parent.children), index, [
180
+ /** @type {unknown} */ (invokeNode),
181
+ /** @type {unknown} */ (wrapper),
182
+ ]);
183
+
184
+ return 'skip';
185
+ }
186
+
187
+ if (live) {
188
+ /** @type {unknown[]} */ (parent.children)[index] = /** @type {unknown} */ (invokeNode);
189
+
190
+ return 'skip';
191
+ }
192
+
193
+ /** @type {unknown[]} */ (parent.children)[index] = /** @type {unknown} */ (wrapper);
194
+
195
+ return;
196
+ });
197
+ };
198
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @typedef {import('unified').Plugin} UPlugin
3
+ */
4
+ import { buildCompiler } from './build-compiler.js';
5
+
6
+ /**
7
+ * @param {string} input
8
+ * @param {import('./types').InternalOptions} options
9
+ *
10
+ * @returns {Promise<{ text: string; codeBlocks: { lang: string; format: string; code: string; name: string }[] }>}
11
+ */
12
+ export async function parseMarkdown(input, options) {
13
+ const markdownCompiler = buildCompiler(options);
14
+ const processed = await markdownCompiler.process(input);
15
+ const liveCode = /** @type {{ lang: string; format: string; code: string; name: string }[]} */ (
16
+ processed.data.liveCode || []
17
+ );
18
+ // @ts-ignore - processed is typed as unknown due to unified processor complexity
19
+ const templateOnly = processed.toString();
20
+
21
+ return { text: templateOnly, codeBlocks: liveCode };
22
+ }