jamdesk 1.1.224 → 1.1.225
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": "jamdesk",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.225",
|
|
4
4
|
"description": "CLI for Jamdesk — build, preview, and deploy documentation sites from MDX. Dev server with hot reload, 50+ components, OpenAPI support, AI search, and Mintlify migration",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jamdesk",
|
|
@@ -37,6 +37,8 @@
|
|
|
37
37
|
* live documentation sites.
|
|
38
38
|
*/
|
|
39
39
|
|
|
40
|
+
import { transform } from '@babel/standalone';
|
|
41
|
+
|
|
40
42
|
/** Name the runtime key guard is bound to inside the generated function. */
|
|
41
43
|
export const KEY_GUARD_NAME = '__jdKey';
|
|
42
44
|
|
|
@@ -326,3 +328,183 @@ export function mdxEvalGuardPlugin({ types: t }: { types: any }) {
|
|
|
326
328
|
},
|
|
327
329
|
};
|
|
328
330
|
}
|
|
331
|
+
|
|
332
|
+
const PARAM_NAME = /^[A-Za-z_$][\w$]*$/;
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* The ONLY place tenant code becomes a function. It checks the exact source
|
|
336
|
+
* it is about to compile, then compiles that same string.
|
|
337
|
+
*
|
|
338
|
+
* Why a second check on top of mdxEvalGuardPlugin: the plugin runs on the
|
|
339
|
+
* author's source, and later steps change that code before it runs (the
|
|
340
|
+
* TypeScript preset erases `declare` statements, the snippet compiler strips
|
|
341
|
+
* import lines and rewrites exports). Scope facts from the earlier pass, like
|
|
342
|
+
* "this name is declared locally", need not hold for the code that runs.
|
|
343
|
+
* This check parses the final JavaScript, applies no transforms, and trusts
|
|
344
|
+
* nothing from the earlier pass. It rejects instead of rewriting, so the
|
|
345
|
+
* runtime key guard must already wrap every dynamic key.
|
|
346
|
+
*/
|
|
347
|
+
export function compileGuardedFunction(
|
|
348
|
+
paramNames: string[],
|
|
349
|
+
body: string
|
|
350
|
+
): (...args: unknown[]) => unknown {
|
|
351
|
+
const source = `${STRICT_MODE_PROLOGUE}\n${body}`;
|
|
352
|
+
assertExecutedCodeIsGuarded(paramNames, source);
|
|
353
|
+
// NOTE: new Function() is intentionally used here to compile tenant MDX
|
|
354
|
+
// components and snippets; `source` is exactly what was checked above.
|
|
355
|
+
return new Function(...paramNames, source) as (...args: unknown[]) => unknown;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function assertExecutedCodeIsGuarded(paramNames: string[], source: string): void {
|
|
359
|
+
for (const name of paramNames) {
|
|
360
|
+
if (!PARAM_NAME.test(name)) reject(`Parameter name "${name}"`);
|
|
361
|
+
}
|
|
362
|
+
if (!paramNames.includes(KEY_GUARD_NAME)) reject('Code without the key guard');
|
|
363
|
+
|
|
364
|
+
const wrapped = `(function (${paramNames.join(', ')}) {\n${source}\n});`;
|
|
365
|
+
try {
|
|
366
|
+
transform(wrapped, {
|
|
367
|
+
babelrc: false,
|
|
368
|
+
configFile: false,
|
|
369
|
+
code: false,
|
|
370
|
+
ast: false,
|
|
371
|
+
sourceType: 'script',
|
|
372
|
+
plugins: [executedCodeCheckPlugin(paramNames.length)],
|
|
373
|
+
});
|
|
374
|
+
} catch (error) {
|
|
375
|
+
if (error instanceof MdxEvalGuardError) throw error;
|
|
376
|
+
// Babel could not parse what new Function is about to compile. Refuse it
|
|
377
|
+
// rather than run code nothing has checked.
|
|
378
|
+
reject('Code that could not be checked');
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Check-only twin of mdxEvalGuardPlugin, for the final JavaScript. Same deny
|
|
384
|
+
* lists, but every rule that rewrote there rejects here.
|
|
385
|
+
*/
|
|
386
|
+
function executedCodeCheckPlugin(paramCount: number) {
|
|
387
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
388
|
+
return function plugin(): any {
|
|
389
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
390
|
+
let wrapper: any = null;
|
|
391
|
+
|
|
392
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
393
|
+
const isGuardCall = (node: any) =>
|
|
394
|
+
node?.type === 'CallExpression' &&
|
|
395
|
+
node.callee?.type === 'Identifier' &&
|
|
396
|
+
node.callee.name === KEY_GUARD_NAME &&
|
|
397
|
+
node.arguments?.length === 1 &&
|
|
398
|
+
node.arguments[0].type !== 'SpreadElement';
|
|
399
|
+
|
|
400
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
401
|
+
const checkComputedKey = (key: any) => {
|
|
402
|
+
if (key?.type === 'StringLiteral') {
|
|
403
|
+
if (FORBIDDEN_PROPERTIES.has(key.value)) reject(`Property "${key.value}"`);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
if (key?.type === 'NumericLiteral') return;
|
|
407
|
+
if (isGuardCall(key)) return;
|
|
408
|
+
reject('Unguarded dynamic property access');
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
412
|
+
const checkMember = (path: any) => {
|
|
413
|
+
const { node } = path;
|
|
414
|
+
if (!node.computed) {
|
|
415
|
+
if (node.property?.type === 'Identifier' &&
|
|
416
|
+
FORBIDDEN_PROPERTIES.has(node.property.name)) {
|
|
417
|
+
reject(`Property "${node.property.name}"`);
|
|
418
|
+
}
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
checkComputedKey(node.property);
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
return {
|
|
425
|
+
visitor: {
|
|
426
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
427
|
+
Program(path: any) {
|
|
428
|
+
// The source must parse as exactly the one wrapper function, so
|
|
429
|
+
// text that closes the wrapper early is refused, not checked as
|
|
430
|
+
// something new Function would never see.
|
|
431
|
+
const body = path.node.body;
|
|
432
|
+
const expr = body.length === 1 && body[0].type === 'ExpressionStatement'
|
|
433
|
+
? body[0].expression : null;
|
|
434
|
+
if (expr?.type !== 'FunctionExpression' ||
|
|
435
|
+
expr.params.length !== paramCount ||
|
|
436
|
+
(path.node.directives?.length ?? 0) > 0) {
|
|
437
|
+
reject('Code that could not be checked');
|
|
438
|
+
}
|
|
439
|
+
wrapper = expr;
|
|
440
|
+
},
|
|
441
|
+
|
|
442
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
443
|
+
Identifier(path: any) {
|
|
444
|
+
const name = path.node.name;
|
|
445
|
+
|
|
446
|
+
if (name === KEY_GUARD_NAME) {
|
|
447
|
+
if (path.parentPath?.node === wrapper && path.listKey === 'params') return;
|
|
448
|
+
const call = path.parentPath;
|
|
449
|
+
if (call?.isCallExpression() && call.node.callee === path.node &&
|
|
450
|
+
isGuardCall(call.node)) {
|
|
451
|
+
const owner = call.parentPath;
|
|
452
|
+
const asMemberKey =
|
|
453
|
+
(owner?.isMemberExpression() || owner?.isOptionalMemberExpression()) &&
|
|
454
|
+
owner.node.computed && owner.node.property === call.node;
|
|
455
|
+
const asPatternKey =
|
|
456
|
+
owner?.isObjectProperty() && owner.node.computed &&
|
|
457
|
+
owner.node.key === call.node && owner.parentPath?.isObjectPattern();
|
|
458
|
+
const binding = path.scope.getBinding(name);
|
|
459
|
+
if ((asMemberKey || asPatternKey) && binding?.kind === 'param' &&
|
|
460
|
+
binding.scope.block === wrapper) {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
reject(`"${KEY_GUARD_NAME}" outside a guarded key`);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (!FORBIDDEN_GLOBALS.has(name)) return;
|
|
468
|
+
if (!path.isReferencedIdentifier() && !path.isBindingIdentifier()) return;
|
|
469
|
+
const binding = path.scope.getBinding(name);
|
|
470
|
+
if (binding && binding.kind !== 'module') return;
|
|
471
|
+
reject(`"${name}"`);
|
|
472
|
+
},
|
|
473
|
+
|
|
474
|
+
MemberExpression: checkMember,
|
|
475
|
+
OptionalMemberExpression: checkMember,
|
|
476
|
+
|
|
477
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
478
|
+
ObjectProperty(path: any) {
|
|
479
|
+
const { node } = path;
|
|
480
|
+
const isPattern = path.parentPath?.isObjectPattern() === true;
|
|
481
|
+
if (node.computed) {
|
|
482
|
+
if (isPattern) checkComputedKey(node.key);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
const name = node.key?.type === 'Identifier' ? node.key.name :
|
|
486
|
+
node.key?.type === 'StringLiteral' ? node.key.value : null;
|
|
487
|
+
if (name === null) return;
|
|
488
|
+
if (isPattern ? FORBIDDEN_PROPERTIES.has(name) : name === '__proto__') {
|
|
489
|
+
reject(`Property "${name}"`);
|
|
490
|
+
}
|
|
491
|
+
},
|
|
492
|
+
|
|
493
|
+
ImportExpression() {
|
|
494
|
+
reject('Dynamic import()');
|
|
495
|
+
},
|
|
496
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
497
|
+
CallExpression(path: any) {
|
|
498
|
+
if (path.node.callee?.type === 'Import') reject('Dynamic import()');
|
|
499
|
+
},
|
|
500
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
501
|
+
MetaProperty(path: any) {
|
|
502
|
+
if (path.node.meta?.name === 'import') reject('import.meta');
|
|
503
|
+
},
|
|
504
|
+
WithStatement() {
|
|
505
|
+
reject('with statements');
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
};
|
|
509
|
+
};
|
|
510
|
+
}
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
mdxEvalGuardPlugin,
|
|
31
31
|
guardPropertyKey,
|
|
32
32
|
KEY_GUARD_NAME,
|
|
33
|
-
|
|
33
|
+
compileGuardedFunction,
|
|
34
34
|
} from './mdx-eval-guard';
|
|
35
35
|
|
|
36
36
|
interface InlineExports {
|
|
@@ -130,24 +130,17 @@ export function compileInlineComponents(
|
|
|
130
130
|
|
|
131
131
|
// Step 3: Create and evaluate the component
|
|
132
132
|
// The component has access to React, jsx runtime, AND built-in MDX components
|
|
133
|
-
//
|
|
134
|
-
// This is similar to how MDX itself works - user content becomes executable code
|
|
133
|
+
// compileGuardedFunction checks the final JavaScript, then compiles it with
|
|
134
|
+
// new Function. This is similar to how MDX itself works - user content becomes executable code
|
|
135
135
|
// Babel's automatic runtime uses _jsx/_jsxs as variable names (with underscore)
|
|
136
136
|
//
|
|
137
137
|
// IMPORTANT: React hooks are intentionally NOT provided here. Inline components
|
|
138
138
|
// are server-rendered during Next.js static export, and hooks only work client-side.
|
|
139
139
|
// Users who need hooks should use snippet files with 'use client' directive instead.
|
|
140
140
|
|
|
141
|
-
const createComponent =
|
|
142
|
-
'React',
|
|
143
|
-
|
|
144
|
-
'_jsxs',
|
|
145
|
-
'_Fragment',
|
|
146
|
-
KEY_GUARD_NAME, // runtime half of mdxEvalGuardPlugin — see mdx-eval-guard.ts
|
|
147
|
-
...builtInNames, // Note, Card, Icon, Tip, Warning, etc.
|
|
148
|
-
// STRICT_MODE_PROLOGUE is load-bearing, not hygiene: without it this
|
|
149
|
-
// body is sloppy-mode and `this` is globalThis. See mdx-eval-guard.ts.
|
|
150
|
-
`${STRICT_MODE_PROLOGUE}\nreturn ${jsFunctionBody}`
|
|
141
|
+
const createComponent = compileGuardedFunction(
|
|
142
|
+
['React', '_jsx', '_jsxs', '_Fragment', KEY_GUARD_NAME, ...builtInNames],
|
|
143
|
+
`return ${jsFunctionBody}`
|
|
151
144
|
);
|
|
152
145
|
|
|
153
146
|
const component = createComponent(
|
|
@@ -159,7 +152,7 @@ export function compileInlineComponents(
|
|
|
159
152
|
...builtInNames.map((n) => builtInComponents[n]) // Pass actual components
|
|
160
153
|
);
|
|
161
154
|
|
|
162
|
-
components[name] = component;
|
|
155
|
+
components[name] = component as AnyComponent;
|
|
163
156
|
} catch (error) {
|
|
164
157
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
165
158
|
errors.push(`Failed to compile inline component "${name}": ${errorMessage}`);
|
|
@@ -24,8 +24,8 @@ import {
|
|
|
24
24
|
mdxEvalGuardPlugin,
|
|
25
25
|
guardPropertyKey,
|
|
26
26
|
KEY_GUARD_NAME,
|
|
27
|
-
STRICT_MODE_PROLOGUE,
|
|
28
27
|
MdxEvalGuardError,
|
|
28
|
+
compileGuardedFunction,
|
|
29
29
|
} from './mdx-eval-guard';
|
|
30
30
|
import { extractUtilityCandidates, MAX_UTILITY_CANDIDATES } from './user-utility-css';
|
|
31
31
|
import { logger } from '../shared/logger';
|
|
@@ -355,17 +355,9 @@ function evaluateSnippetCode(
|
|
|
355
355
|
builtInNames: string[],
|
|
356
356
|
builtInValues: AnyComponent[]
|
|
357
357
|
): unknown {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
'_jsx',
|
|
362
|
-
'_jsxs',
|
|
363
|
-
'_Fragment',
|
|
364
|
-
KEY_GUARD_NAME, // runtime half of mdxEvalGuardPlugin — see mdx-eval-guard.ts
|
|
365
|
-
...builtInNames,
|
|
366
|
-
// STRICT_MODE_PROLOGUE is load-bearing, not hygiene: without it this
|
|
367
|
-
// body is sloppy-mode and `this` is globalThis. See mdx-eval-guard.ts.
|
|
368
|
-
`${STRICT_MODE_PROLOGUE}\n${code}`
|
|
358
|
+
const fn = compileGuardedFunction(
|
|
359
|
+
['React', '_jsx', '_jsxs', '_Fragment', KEY_GUARD_NAME, ...builtInNames],
|
|
360
|
+
code
|
|
369
361
|
);
|
|
370
362
|
return fn(React, jsxRuntime.jsx, jsxRuntime.jsxs, jsxRuntime.Fragment, guardPropertyKey, ...builtInValues);
|
|
371
363
|
}
|