repl-sdk 1.1.2 → 1.2.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.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "repl-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -42,28 +42,28 @@
|
|
|
42
42
|
"typescript": "^5.9.3",
|
|
43
43
|
"vite": "^7.3.1",
|
|
44
44
|
"vite-plugin-dts": "4.5.4",
|
|
45
|
-
"vitest": "^
|
|
45
|
+
"vitest": "^4.0.18"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@codemirror/autocomplete": "6.20.0",
|
|
49
|
-
"@codemirror/commands": "6.10.1",
|
|
48
|
+
"@codemirror/autocomplete": "^6.20.0",
|
|
49
|
+
"@codemirror/commands": "^6.10.1",
|
|
50
50
|
"@codemirror/lang-html": "^6.4.11",
|
|
51
|
-
"@codemirror/lang-javascript": "6.2.4",
|
|
52
|
-
"@codemirror/lang-markdown": "6.5.0",
|
|
51
|
+
"@codemirror/lang-javascript": "^6.2.4",
|
|
52
|
+
"@codemirror/lang-markdown": "^6.5.0",
|
|
53
53
|
"@codemirror/lang-vue": "^0.1.3",
|
|
54
54
|
"@codemirror/lang-yaml": "^6.1.2",
|
|
55
55
|
"@codemirror/language": "^6.12.1",
|
|
56
56
|
"@codemirror/language-data": "^6.5.2",
|
|
57
|
-
"@codemirror/lint": "^6.9.
|
|
58
|
-
"@codemirror/search": "6.
|
|
59
|
-
"@codemirror/state": "6.5.
|
|
60
|
-
"@codemirror/view": "6.39.
|
|
57
|
+
"@codemirror/lint": "^6.9.3",
|
|
58
|
+
"@codemirror/search": "^6.6.0",
|
|
59
|
+
"@codemirror/state": "^6.5.4",
|
|
60
|
+
"@codemirror/view": "^6.39.12",
|
|
61
61
|
"@lezer/common": "^1.5.0",
|
|
62
62
|
"@lezer/highlight": "^1.2.3",
|
|
63
63
|
"@lezer/html": "^1.3.13",
|
|
64
64
|
"@lezer/markdown": "^1.6.3",
|
|
65
65
|
"@replit/codemirror-lang-svelte": "^6.0.0",
|
|
66
|
-
"@shikijs/rehype": "^3.
|
|
66
|
+
"@shikijs/rehype": "^3.22.0",
|
|
67
67
|
"change-case": "^5.4.4",
|
|
68
68
|
"codemirror": "^6.0.2",
|
|
69
69
|
"codemirror-lang-mermaid": "^0.5.0",
|
|
@@ -73,7 +73,9 @@
|
|
|
73
73
|
"mdast": "^3.0.0",
|
|
74
74
|
"mime": "^4.0.7",
|
|
75
75
|
"package-name-regex": "^5.0.0",
|
|
76
|
+
"rehype-autolink-headings": "^7.1.0",
|
|
76
77
|
"rehype-raw": "^7.0.0",
|
|
78
|
+
"rehype-slug": "^6.0.0",
|
|
77
79
|
"rehype-stringify": "^10.0.1",
|
|
78
80
|
"remark-gfm": "^4.0.1",
|
|
79
81
|
"remark-parse": "^11.0.0",
|
|
@@ -84,9 +86,9 @@
|
|
|
84
86
|
"unified": "^11.0.5",
|
|
85
87
|
"unist-util-visit": "^5.0.0",
|
|
86
88
|
"vfile": "^6.0.3",
|
|
87
|
-
"codemirror-lang-glimdown": "2.0.
|
|
88
|
-
"codemirror-lang-glimmer-js": "2.0.
|
|
89
|
-
"codemirror-lang-glimmer": "2.0.
|
|
89
|
+
"codemirror-lang-glimdown": "^2.0.3",
|
|
90
|
+
"codemirror-lang-glimmer-js": "^2.0.3",
|
|
91
|
+
"codemirror-lang-glimmer": "^2.0.3"
|
|
90
92
|
},
|
|
91
93
|
"volta": {
|
|
92
94
|
"extends": "../../package.json"
|
|
@@ -40,6 +40,39 @@ export function buildCompiler(options) {
|
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
// Mark raw HTML components (PascalCase) before remarkRehype processes them
|
|
44
|
+
// @ts-ignore - unified processor types are complex and change as plugins are added
|
|
45
|
+
compiler = compiler.use(() => (tree) => {
|
|
46
|
+
visit(tree, 'html', function (node) {
|
|
47
|
+
// Check if this html node is a PascalCase component
|
|
48
|
+
if (typeof node.value === 'string' && node.value.match(/^<[A-Z][a-zA-Z0-9]/)) {
|
|
49
|
+
// Add a marker to the node's data that remarkRehype will preserve
|
|
50
|
+
// remark-rehype with allowDangerousHtml will turn this into a text node,
|
|
51
|
+
// and the data should be preserved
|
|
52
|
+
if (!node.data) node.data = {};
|
|
53
|
+
node.data.isPascalCaseComponent = true;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// After markinghtml nodes, also visit paragraphs to mark their children
|
|
58
|
+
visit(tree, 'paragraph', (paragraph) => {
|
|
59
|
+
if (paragraph.children) {
|
|
60
|
+
for (let i = 0; i < paragraph.children.length; i++) {
|
|
61
|
+
const child = paragraph.children[i];
|
|
62
|
+
|
|
63
|
+
if (
|
|
64
|
+
child.type === 'html' &&
|
|
65
|
+
typeof child.value === 'string' &&
|
|
66
|
+
child.value.match(/^<[A-Z][a-zA-Z0-9]/)
|
|
67
|
+
) {
|
|
68
|
+
if (!child.data) child.data = {};
|
|
69
|
+
child.data.isPascalCaseComponent = true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
43
76
|
// TODO: we only want to do this when we have pre > code.
|
|
44
77
|
// code can exist inline.
|
|
45
78
|
// @ts-ignore - unified processor types are complex and change as plugins are added
|
|
@@ -65,6 +98,10 @@ export function buildCompiler(options) {
|
|
|
65
98
|
// However, it also changes all the nodes, so we need another pass
|
|
66
99
|
// to make sure our Glimmer-aware nodes are in tact
|
|
67
100
|
// @ts-ignore - unified processor types are complex and change as plugins are added
|
|
101
|
+
// remark rehype is needed to convert markdown to HTML
|
|
102
|
+
// However, it also changes all the nodes, so we need another pass
|
|
103
|
+
// to make sure our Glimmer-aware nodes are in tact
|
|
104
|
+
// @ts-ignore - unified processor types are complex and change as plugins are added
|
|
68
105
|
compiler = compiler.use(remarkRehype, { allowDangerousHtml: true });
|
|
69
106
|
|
|
70
107
|
// Convert invocables to raw format, so Glimmer can invoke them
|
|
@@ -81,6 +118,15 @@ export function buildCompiler(options) {
|
|
|
81
118
|
return 'skip';
|
|
82
119
|
}
|
|
83
120
|
|
|
121
|
+
// Check for PascalCase elements FIRST before checking for code elements
|
|
122
|
+
const tagName = /** @type {string | undefined} */ (nodeObj.tagName);
|
|
123
|
+
|
|
124
|
+
if (tagName && /^[A-Z]/.test(tagName)) {
|
|
125
|
+
nodeObj.type = 'glimmer_raw';
|
|
126
|
+
|
|
127
|
+
return 'skip';
|
|
128
|
+
}
|
|
129
|
+
|
|
84
130
|
if (nodeObj.type === 'element' || ('tagName' in nodeObj && nodeObj.tagName === 'code')) {
|
|
85
131
|
if (properties?.[/** @type {string} */ (/** @type {unknown} */ (GLIMDOWN_RENDER))]) {
|
|
86
132
|
nodeObj.type = 'glimmer_raw';
|
|
@@ -91,19 +137,32 @@ export function buildCompiler(options) {
|
|
|
91
137
|
return 'skip';
|
|
92
138
|
}
|
|
93
139
|
|
|
94
|
-
|
|
95
|
-
|
|
140
|
+
// Check for nodes with values (text, raw, html, etc.)
|
|
141
|
+
if ('value' in nodeObj && typeof nodeObj.value === 'string') {
|
|
142
|
+
// Check if this raw node was marked as a PascalCase component in remark phase
|
|
143
|
+
const nodeData = /** @type {Record<string, unknown> | undefined} */ (
|
|
144
|
+
typeof node === 'object' && node !== null && 'data' in node ? node.data : undefined
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
if (nodeData?.isPascalCaseComponent) {
|
|
148
|
+
nodeObj.type = 'glimmer_raw';
|
|
149
|
+
|
|
150
|
+
return 'skip';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Match raw PascalCase components in text that haven't been escaped yet
|
|
154
|
+
// Pattern: <PascalCaseName followed by whitespace, > or @
|
|
96
155
|
if (
|
|
97
|
-
'
|
|
98
|
-
|
|
99
|
-
nodeObj.value.match(/<\/?[_A-Z:0-9].*>/g)
|
|
156
|
+
(nodeObj.type === 'text' || nodeObj.type === 'raw' || nodeObj.type === 'html') &&
|
|
157
|
+
nodeObj.value.match(/<[A-Z][a-zA-Z0-9]*(\s|>|@)/)
|
|
100
158
|
) {
|
|
101
159
|
nodeObj.type = 'glimmer_raw';
|
|
102
|
-
}
|
|
103
160
|
|
|
104
|
-
|
|
161
|
+
return 'skip';
|
|
162
|
+
}
|
|
105
163
|
|
|
106
|
-
|
|
164
|
+
// Let normal nodes be processed for escaping
|
|
165
|
+
return;
|
|
107
166
|
}
|
|
108
167
|
|
|
109
168
|
return;
|
|
@@ -1,22 +1,55 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @typedef {
|
|
2
|
+
* @typedef {object} CodeBlock
|
|
3
|
+
* @property {string} lang
|
|
4
|
+
* @property {string} format
|
|
5
|
+
* @property {string} code
|
|
6
|
+
* @property {string} name
|
|
3
7
|
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {object} ParseResult
|
|
11
|
+
* @property {string} text
|
|
12
|
+
* @property {CodeBlock[]} codeBlocks
|
|
13
|
+
*/
|
|
14
|
+
|
|
4
15
|
import { buildCompiler } from './build-compiler.js';
|
|
5
16
|
|
|
6
17
|
/**
|
|
7
18
|
* @param {string} input
|
|
8
19
|
* @param {import('./types').InternalOptions} options
|
|
9
|
-
*
|
|
10
|
-
* @returns {Promise<{ text: string; codeBlocks: { lang: string; format: string; code: string; name: string }[] }>}
|
|
20
|
+
* @returns {Promise<ParseResult>}
|
|
11
21
|
*/
|
|
12
22
|
export async function parseMarkdown(input, options) {
|
|
13
23
|
const markdownCompiler = buildCompiler(options);
|
|
14
24
|
const processed = await markdownCompiler.process(input);
|
|
15
|
-
const liveCode = /** @type {
|
|
16
|
-
processed.data.liveCode || []
|
|
17
|
-
);
|
|
25
|
+
const liveCode = /** @type {CodeBlock[]} */ (processed.data.liveCode || []);
|
|
18
26
|
// @ts-ignore - processed is typed as unknown due to unified processor complexity
|
|
19
|
-
|
|
27
|
+
let templateOnly = processed.toString();
|
|
28
|
+
|
|
29
|
+
// Unescape PascalCase components that had only the opening < HTML-entity escaped
|
|
30
|
+
// BUT only outside of <pre><code> blocks where escaping should be preserved
|
|
31
|
+
// (inline <code> tags should have components unescaped)
|
|
32
|
+
// Split by <pre><code>...</code></pre> to exclude only code blocks
|
|
33
|
+
const parts = templateOnly.split(/(<pre[^>]*>.*?<\/pre>)/is);
|
|
34
|
+
|
|
35
|
+
for (let i = 0; i < parts.length; i++) {
|
|
36
|
+
const part = parts[i];
|
|
37
|
+
|
|
38
|
+
// Only process parts that are NOT pre blocks (odd indices are pre blocks)
|
|
39
|
+
if (i % 2 === 0 && part) {
|
|
40
|
+
// Pattern: <ComponentName ... / > (only < is escaped as <)
|
|
41
|
+
parts[i] = part.replace(/<([A-Z][a-zA-Z0-9]*\s[^<]*?)>/g, (match, content) => {
|
|
42
|
+
// Only unescape if it contains @ (attribute) indicating a component
|
|
43
|
+
if (content.includes('@')) {
|
|
44
|
+
return `<${content}>`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return match;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
templateOnly = parts.join('');
|
|
20
53
|
|
|
21
54
|
return { text: templateOnly, codeBlocks: liveCode };
|
|
22
55
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import rehypeShiki from '@shikijs/rehype';
|
|
2
2
|
import { stripIndent } from 'common-tags';
|
|
3
3
|
import { visit } from 'unist-util-visit';
|
|
4
|
-
import { describe, expect as errorExpect, it } from 'vitest';
|
|
4
|
+
import { beforeEach, describe, expect as errorExpect, it } from 'vitest';
|
|
5
5
|
|
|
6
|
+
import { resetIdCounter } from '../../utils.js';
|
|
6
7
|
import { parseMarkdown } from './parse.js';
|
|
7
8
|
import { buildCodeFenceMetaUtils } from './utils.js';
|
|
8
9
|
|
|
@@ -41,6 +42,174 @@ const defaults = {
|
|
|
41
42
|
ALLOWED_FORMATS,
|
|
42
43
|
};
|
|
43
44
|
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
resetIdCounter();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('default features', () => {
|
|
50
|
+
it('allows one-line-component invocation', { timeout: 10_000 }, async () => {
|
|
51
|
+
const result = await parseMarkdown(`<APIDocs @package="ember-primitives" @name="Avatar" />`, {
|
|
52
|
+
...defaults,
|
|
53
|
+
rehypePlugins: [[rehypeShiki, { theme: 'github-dark' }]],
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
expect(result.codeBlocks).toMatchInlineSnapshot(`[]`);
|
|
57
|
+
expect(result.text).toMatchInlineSnapshot(
|
|
58
|
+
`"<p><APIDocs @package="ember-primitives" @name="Avatar" /></p>"`
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('allows multi-line-component invocation', async () => {
|
|
63
|
+
const result = await parseMarkdown(
|
|
64
|
+
[`<APIDocs`, `@package="ember-primitives"`, `@name="Avatar"`, `/>`].join('\n'),
|
|
65
|
+
{
|
|
66
|
+
...defaults,
|
|
67
|
+
rehypePlugins: [[rehypeShiki, { theme: 'github-dark' }]],
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
expect(result.codeBlocks).toMatchInlineSnapshot(`[]`);
|
|
72
|
+
expect(result.text).toMatchInlineSnapshot(`
|
|
73
|
+
"<p><APIDocs
|
|
74
|
+
@package="ember-primitives"
|
|
75
|
+
@name="Avatar"
|
|
76
|
+
/></p>"
|
|
77
|
+
`);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('allows components inside code blocks', async () => {
|
|
81
|
+
const result = await parseMarkdown(
|
|
82
|
+
[
|
|
83
|
+
'# Hello',
|
|
84
|
+
'',
|
|
85
|
+
'<code>',
|
|
86
|
+
' <Shadowed @includeStyles={{true}}>',
|
|
87
|
+
' the shadow realm',
|
|
88
|
+
' </Shadowed>',
|
|
89
|
+
'</code>',
|
|
90
|
+
].join('\n'),
|
|
91
|
+
{
|
|
92
|
+
...defaults,
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
expect(result.codeBlocks).toMatchInlineSnapshot(`[]`);
|
|
97
|
+
expect(result.text).toMatchInlineSnapshot(`
|
|
98
|
+
"<h1 id="hello">Hello</h1>
|
|
99
|
+
<code>
|
|
100
|
+
<Shadowed @includeStyles={{true}}>
|
|
101
|
+
the shadow realm
|
|
102
|
+
</Shadowed>
|
|
103
|
+
</code>"
|
|
104
|
+
`);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('does not', () => {
|
|
108
|
+
it(`mistakenly transform text in codefences (previewed text is transformed though)`, async () => {
|
|
109
|
+
const result = await parseMarkdown(
|
|
110
|
+
[
|
|
111
|
+
'# Hello',
|
|
112
|
+
'',
|
|
113
|
+
'```gjs live preview',
|
|
114
|
+
"import { Shadowed, PortalTargets } from 'ember-primitives';",
|
|
115
|
+
'',
|
|
116
|
+
'<template>',
|
|
117
|
+
' <Shadowed @includeStyles={{true}}>',
|
|
118
|
+
' the shadow realm',
|
|
119
|
+
' </Shadowed>',
|
|
120
|
+
'</template>',
|
|
121
|
+
].join('\n'),
|
|
122
|
+
{
|
|
123
|
+
...defaults,
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
expect(result.codeBlocks).toMatchInlineSnapshot(`
|
|
128
|
+
[
|
|
129
|
+
{
|
|
130
|
+
"code": "import { Shadowed, PortalTargets } from 'ember-primitives';
|
|
131
|
+
|
|
132
|
+
<template>
|
|
133
|
+
<Shadowed @includeStyles={{true}}>
|
|
134
|
+
the shadow realm
|
|
135
|
+
</Shadowed>
|
|
136
|
+
</template>",
|
|
137
|
+
"flavor": undefined,
|
|
138
|
+
"format": "gjs",
|
|
139
|
+
"meta": "live preview",
|
|
140
|
+
"placeholderId": "repl_1",
|
|
141
|
+
},
|
|
142
|
+
]
|
|
143
|
+
`);
|
|
144
|
+
expect(result.text).toMatchInlineSnapshot(`
|
|
145
|
+
"<h1 id="hello">Hello</h1>
|
|
146
|
+
<div id="repl_1" class="repl-sdk__demo"></div>
|
|
147
|
+
<div class="repl-sdk__snippet" data-repl-output><pre><code class="language-gjs">import { Shadowed, PortalTargets } from 'ember-primitives';
|
|
148
|
+
|
|
149
|
+
<template>
|
|
150
|
+
<Shadowed @includeStyles=\\{{true}}>
|
|
151
|
+
the shadow realm
|
|
152
|
+
</Shadowed>
|
|
153
|
+
</template>
|
|
154
|
+
</code></pre></div>"
|
|
155
|
+
`);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it(`mistakenly transform text in codefences`, async () => {
|
|
159
|
+
const result = await parseMarkdown(
|
|
160
|
+
[
|
|
161
|
+
'# Hello',
|
|
162
|
+
'',
|
|
163
|
+
'```gjs live',
|
|
164
|
+
'import { Hello } from "somewhere";',
|
|
165
|
+
'',
|
|
166
|
+
'<template>',
|
|
167
|
+
' <Hello />',
|
|
168
|
+
'</template>',
|
|
169
|
+
].join('\n'),
|
|
170
|
+
{
|
|
171
|
+
...defaults,
|
|
172
|
+
rehypePlugins: [[rehypeShiki, { theme: 'github-dark' }]],
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
expect(result.codeBlocks).toMatchInlineSnapshot(`
|
|
177
|
+
[
|
|
178
|
+
{
|
|
179
|
+
"code": "import { Hello } from "somewhere";
|
|
180
|
+
|
|
181
|
+
<template>
|
|
182
|
+
<Hello />
|
|
183
|
+
</template>",
|
|
184
|
+
"flavor": undefined,
|
|
185
|
+
"format": "gjs",
|
|
186
|
+
"meta": "live",
|
|
187
|
+
"placeholderId": "repl_1",
|
|
188
|
+
},
|
|
189
|
+
]
|
|
190
|
+
`);
|
|
191
|
+
expect(result.text).toMatchInlineSnapshot(`
|
|
192
|
+
"<h1 id="hello">Hello</h1>
|
|
193
|
+
<div id="repl_1" class="repl-sdk__demo"></div>"
|
|
194
|
+
`);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('mistakenly transform a component text in backticks', async () => {
|
|
198
|
+
const result = await parseMarkdown('## `<Hello @foo="two" />`', {
|
|
199
|
+
...defaults,
|
|
200
|
+
rehypePlugins: [[rehypeShiki, { theme: 'github-dark' }]],
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
expect(result).toMatchInlineSnapshot(`
|
|
204
|
+
{
|
|
205
|
+
"codeBlocks": [],
|
|
206
|
+
"text": "<h2 id="hello-foo-two"><code><Hello @foo="two" /></code></h2>",
|
|
207
|
+
}
|
|
208
|
+
`);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
44
213
|
describe('options', () => {
|
|
45
214
|
describe('remarkPlugins', () => {
|
|
46
215
|
it('works', async () => {
|
|
@@ -233,7 +402,7 @@ describe('options', () => {
|
|
|
233
402
|
|
|
234
403
|
expect(result.text).toMatchInlineSnapshot(`
|
|
235
404
|
"<h1 id="title">Title</h1>
|
|
236
|
-
<div id="
|
|
405
|
+
<div id="repl_1" class="repl-sdk__demo"></div>"
|
|
237
406
|
`);
|
|
238
407
|
|
|
239
408
|
assertCodeBlocks(result.codeBlocks, [
|
|
@@ -311,7 +480,7 @@ describe('options', () => {
|
|
|
311
480
|
|
|
312
481
|
expect(result.text).toMatchInlineSnapshot(`
|
|
313
482
|
"<h1 id="title">Title</h1>
|
|
314
|
-
<div id="
|
|
483
|
+
<div id="repl_1" class="repl-sdk__demo"></div>
|
|
315
484
|
<Demo />"
|
|
316
485
|
`);
|
|
317
486
|
|
|
@@ -340,7 +509,7 @@ describe('options', () => {
|
|
|
340
509
|
|
|
341
510
|
expect(result.text).toMatchInlineSnapshot(`
|
|
342
511
|
"<p>hi</p>
|
|
343
|
-
<div id="
|
|
512
|
+
<div id="repl_1" class="repl-sdk__demo"></div>
|
|
344
513
|
<div class="repl-sdk__snippet" data-repl-output><pre><code class="language-gjs">import Component from '@glimmer/component';
|
|
345
514
|
import { on } from '@ember/modifier';
|
|
346
515
|
|
package/src/utils.js
CHANGED
|
@@ -17,6 +17,10 @@ export function nextId() {
|
|
|
17
17
|
return `repl_${i}`;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
export function resetIdCounter() {
|
|
21
|
+
i = 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
20
24
|
export const fakeDomain = 'repl.sdk';
|
|
21
25
|
export const tgzPrefix = 'file:///tgz.repl.sdk/';
|
|
22
26
|
export const unzippedPrefix = 'file:///tgz.repl.sdk/unzipped';
|