auramaxx 1.0.0-alpha.4
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/LICENSE +26 -0
- package/README.md +112 -0
- package/bin/aurawallet.js +121 -0
- package/docs/ADAPTERS.md +467 -0
- package/docs/API.md +2679 -0
- package/docs/APPS.md +198 -0
- package/docs/ARCHITECTURE.md +350 -0
- package/docs/AUTH.md +698 -0
- package/docs/BEST-PRACTICES.md +121 -0
- package/docs/CLI.md +61 -0
- package/docs/DEVELOPING-APPS.md +452 -0
- package/docs/EXTENSION.md +97 -0
- package/docs/JOBS.md +33 -0
- package/docs/MCP.md +76 -0
- package/docs/PROTOCOL.md +142 -0
- package/docs/SETUP.md +219 -0
- package/docs/WORKSPACE.md +672 -0
- package/docs/agent-auth.md +63 -0
- package/docs/aura-file.md +48 -0
- package/docs/credentials.md +53 -0
- package/docs/external/getting-started.md +65 -0
- package/docs/external/overview.md +45 -0
- package/docs/external/use-cases.md +48 -0
- package/docs/external/why-aura.md +35 -0
- package/docs/jobs/connect-agent.md +77 -0
- package/docs/jobs/migrate-from-dotenv.md +79 -0
- package/docs/jobs/recover-from-lockout.md +72 -0
- package/docs/jobs/secure-ci.md +63 -0
- package/docs/oauth2.md +42 -0
- package/docs/passkeys.md +60 -0
- package/docs/security.md +540 -0
- package/docs/specs/aura-open-protocol.md +61 -0
- package/docs/specs/aura-provider-plugin.md +24 -0
- package/docs/specs/aura-registry-model.md +31 -0
- package/docs/specs/fixtures/invalid-bad-key.aura +1 -0
- package/docs/specs/fixtures/invalid-bad-unicode-escape.aura +1 -0
- package/docs/specs/fixtures/invalid-duplicate-key.aura +2 -0
- package/docs/specs/fixtures/valid-basic.aura +4 -0
- package/docs/specs/fixtures/valid-provider-ref.aura +1 -0
- package/docs/specs/fixtures/valid-quoted-escapes.aura +2 -0
- package/docs/templates/RELEASE_NOTES_TEMPLATE.md +22 -0
- package/docs/totp.md +40 -0
- package/docs/wallet/AI.md +508 -0
- package/docs/wallet/DEVELOPING-STRATEGIES.md +713 -0
- package/docs/wallet/README.md +47 -0
- package/docs/wallet/STRATEGY.md +89 -0
- package/next.config.ts +21 -0
- package/package.json +151 -0
- package/postcss.config.mjs +8 -0
- package/prisma/migrations/20260214170000_baseline/migration.sql +511 -0
- package/prisma/migrations/20260216214537_add_passkey_model/migration.sql +18 -0
- package/prisma/migrations/20260217150500_add_credential_access_audit/migration.sql +31 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +447 -0
- package/public/logo-chevron.svg +31 -0
- package/public/logo-concentric.svg +31 -0
- package/public/logo-crosshatch.svg +39 -0
- package/public/logo-dashed.svg +39 -0
- package/public/logo-horizontal.svg +31 -0
- package/public/logo-m56.svg +64 -0
- package/public/logo.webp +0 -0
- package/scripts/add-app.js +245 -0
- package/scripts/init.sh +57 -0
- package/scripts/migrate-apikeys-to-credentials.ts +35 -0
- package/scripts/sandbox-agent-flow.sh +235 -0
- package/scripts/sandbox.sh +175 -0
- package/scripts/validate-job-docs.mjs +125 -0
- package/server/abi/SwapHelper.json +438 -0
- package/server/cli/approval.ts +447 -0
- package/server/cli/commands/app.ts +204 -0
- package/server/cli/commands/cron.ts +24 -0
- package/server/cli/commands/doctor.ts +1007 -0
- package/server/cli/commands/env.ts +456 -0
- package/server/cli/commands/init.ts +752 -0
- package/server/cli/commands/mcp.ts +125 -0
- package/server/cli/commands/restore.ts +314 -0
- package/server/cli/commands/shell-hook.ts +468 -0
- package/server/cli/commands/start.ts +62 -0
- package/server/cli/commands/status.ts +59 -0
- package/server/cli/commands/stop.ts +14 -0
- package/server/cli/commands/token.ts +180 -0
- package/server/cli/commands/unlock.ts +49 -0
- package/server/cli/commands/vault.ts +417 -0
- package/server/cli/index.ts +328 -0
- package/server/cli/lib/aura-parser.ts +64 -0
- package/server/cli/lib/credential-create.ts +74 -0
- package/server/cli/lib/credential-resolve.ts +254 -0
- package/server/cli/lib/dotenv-migrate.ts +116 -0
- package/server/cli/lib/dotenv-parser.ts +146 -0
- package/server/cli/lib/http.ts +91 -0
- package/server/cli/lib/init-steps.ts +76 -0
- package/server/cli/lib/local-agent-trust.ts +45 -0
- package/server/cli/lib/process.ts +136 -0
- package/server/cli/lib/prompt.ts +85 -0
- package/server/cli/lib/theme.ts +240 -0
- package/server/cli/socket.ts +570 -0
- package/server/cli/transport-client.ts +50 -0
- package/server/cron/index.ts +137 -0
- package/server/cron/job.ts +31 -0
- package/server/cron/jobs/balance-sync.ts +436 -0
- package/server/cron/jobs/incoming-scan.ts +506 -0
- package/server/cron/jobs/native-price.ts +70 -0
- package/server/cron/jobs/orphan-cleanup.ts +40 -0
- package/server/cron/jobs/strategy-runner.ts +175 -0
- package/server/cron/scheduler.ts +125 -0
- package/server/index.ts +406 -0
- package/server/lib/adapters/factory.ts +110 -0
- package/server/lib/adapters/index.ts +19 -0
- package/server/lib/adapters/router.ts +297 -0
- package/server/lib/adapters/telegram.ts +645 -0
- package/server/lib/adapters/types.ts +89 -0
- package/server/lib/adapters/webhook.ts +95 -0
- package/server/lib/address.ts +49 -0
- package/server/lib/agent-auth/contracts.ts +1194 -0
- package/server/lib/agent-profiles.ts +328 -0
- package/server/lib/ai.ts +285 -0
- package/server/lib/api-registry/contracts.ts +86 -0
- package/server/lib/api-registry/validation.ts +172 -0
- package/server/lib/apikey-migration.ts +189 -0
- package/server/lib/app-installer.ts +505 -0
- package/server/lib/app-tokens.ts +247 -0
- package/server/lib/auth.ts +314 -0
- package/server/lib/batch.ts +242 -0
- package/server/lib/cold.ts +874 -0
- package/server/lib/config.ts +381 -0
- package/server/lib/credential-access-audit.ts +85 -0
- package/server/lib/credential-access-policy.ts +110 -0
- package/server/lib/credential-health.ts +343 -0
- package/server/lib/credential-import.ts +487 -0
- package/server/lib/credential-scope.ts +87 -0
- package/server/lib/credential-shares.ts +190 -0
- package/server/lib/credential-transport.ts +342 -0
- package/server/lib/credential-vault.ts +77 -0
- package/server/lib/credentials.ts +333 -0
- package/server/lib/crypto.ts +8 -0
- package/server/lib/db.ts +15 -0
- package/server/lib/defaults.ts +366 -0
- package/server/lib/dex/index.ts +80 -0
- package/server/lib/dex/relay.ts +235 -0
- package/server/lib/dex/types.ts +59 -0
- package/server/lib/dex/uniswap.ts +370 -0
- package/server/lib/e2e-agent/artifacts.ts +36 -0
- package/server/lib/e2e-agent/contracts.ts +112 -0
- package/server/lib/e2e-agent/validation.ts +135 -0
- package/server/lib/encrypt.ts +128 -0
- package/server/lib/error.ts +20 -0
- package/server/lib/events.ts +205 -0
- package/server/lib/hot.ts +357 -0
- package/server/lib/key-fingerprint.ts +28 -0
- package/server/lib/logger.ts +331 -0
- package/server/lib/network.ts +137 -0
- package/server/lib/notifications.ts +219 -0
- package/server/lib/oauth2-refresh.ts +241 -0
- package/server/lib/oursecret.ts +54 -0
- package/server/lib/passkey-credential.ts +360 -0
- package/server/lib/passkey.ts +68 -0
- package/server/lib/permissions.ts +248 -0
- package/server/lib/pino.ts +24 -0
- package/server/lib/policy-preview.ts +138 -0
- package/server/lib/price.ts +338 -0
- package/server/lib/prices.ts +34 -0
- package/server/lib/project-scope.ts +239 -0
- package/server/lib/resolve-action.ts +427 -0
- package/server/lib/resolve.ts +36 -0
- package/server/lib/sessions.ts +632 -0
- package/server/lib/solana/connection.ts +26 -0
- package/server/lib/solana/jupiter.ts +128 -0
- package/server/lib/solana/transfer.ts +108 -0
- package/server/lib/solana/wallet.ts +136 -0
- package/server/lib/strategy/emits.ts +21 -0
- package/server/lib/strategy/engine.ts +1305 -0
- package/server/lib/strategy/executor.ts +115 -0
- package/server/lib/strategy/hook-context.ts +158 -0
- package/server/lib/strategy/hooks.ts +990 -0
- package/server/lib/strategy/index.ts +28 -0
- package/server/lib/strategy/installer.ts +305 -0
- package/server/lib/strategy/loader.ts +256 -0
- package/server/lib/strategy/message.ts +235 -0
- package/server/lib/strategy/repository.ts +218 -0
- package/server/lib/strategy/session-logger.ts +693 -0
- package/server/lib/strategy/sources.ts +288 -0
- package/server/lib/strategy/state.ts +189 -0
- package/server/lib/strategy/templates.ts +403 -0
- package/server/lib/strategy/tick.ts +404 -0
- package/server/lib/strategy/types.ts +230 -0
- package/server/lib/swap.ts +3 -0
- package/server/lib/temp.ts +86 -0
- package/server/lib/token-metadata.ts +86 -0
- package/server/lib/token-safety.ts +200 -0
- package/server/lib/token-search.ts +444 -0
- package/server/lib/totp.ts +194 -0
- package/server/lib/transactions.ts +123 -0
- package/server/lib/transport.ts +75 -0
- package/server/lib/txhistory/decoder.ts +262 -0
- package/server/lib/txhistory/enricher.ts +652 -0
- package/server/lib/txhistory/index.ts +391 -0
- package/server/lib/txhistory/signatures.ts +59 -0
- package/server/lib/verified-summary.ts +421 -0
- package/server/mcp/profile-policy.ts +30 -0
- package/server/mcp/server.ts +619 -0
- package/server/mcp/tools.ts +523 -0
- package/server/middleware/auth.ts +119 -0
- package/server/middleware/requestLogger.ts +84 -0
- package/server/routes/actions.ts +459 -0
- package/server/routes/adapters.ts +703 -0
- package/server/routes/addressbook.ts +113 -0
- package/server/routes/ai.ts +34 -0
- package/server/routes/apikeys.ts +295 -0
- package/server/routes/apps.ts +601 -0
- package/server/routes/auth.ts +457 -0
- package/server/routes/backup.ts +340 -0
- package/server/routes/batch.ts +270 -0
- package/server/routes/bookmarks.ts +162 -0
- package/server/routes/credential-shares.ts +198 -0
- package/server/routes/credential-vaults.ts +154 -0
- package/server/routes/credentials.ts +1290 -0
- package/server/routes/dashboard.ts +71 -0
- package/server/routes/defaults.ts +124 -0
- package/server/routes/fund.ts +229 -0
- package/server/routes/import.ts +352 -0
- package/server/routes/launch.ts +665 -0
- package/server/routes/lock.ts +54 -0
- package/server/routes/logs.ts +68 -0
- package/server/routes/nuke.ts +111 -0
- package/server/routes/passkey-credentials.ts +99 -0
- package/server/routes/passkey.ts +346 -0
- package/server/routes/portfolio.ts +217 -0
- package/server/routes/price.ts +63 -0
- package/server/routes/resolve.ts +31 -0
- package/server/routes/security.ts +45 -0
- package/server/routes/send-evm.ts +241 -0
- package/server/routes/send-solana.ts +281 -0
- package/server/routes/send.ts +178 -0
- package/server/routes/setup.ts +210 -0
- package/server/routes/strategy.ts +894 -0
- package/server/routes/swap-evm.ts +353 -0
- package/server/routes/swap-solana.ts +177 -0
- package/server/routes/swap.ts +356 -0
- package/server/routes/token.ts +247 -0
- package/server/routes/unlock.ts +403 -0
- package/server/routes/wallet-assets.ts +361 -0
- package/server/routes/wallet-transactions.ts +515 -0
- package/server/routes/wallet.ts +710 -0
- package/server/types.ts +146 -0
- package/skills/aurawallet/SKILL.md +739 -0
- package/skills/aurawallet-setup/SKILL.md +74 -0
- package/skills/security-review/SKILL.md +148 -0
- package/src/app/api/agent-requests/route.ts +30 -0
- package/src/app/api/apps/install/route.ts +126 -0
- package/src/app/api/apps/manifests/route.ts +16 -0
- package/src/app/api/apps/static/[...path]/route.ts +57 -0
- package/src/app/api/events/route.ts +92 -0
- package/src/app/api/page.tsx +212 -0
- package/src/app/api/workspace/[id]/apps/[wid]/route.ts +119 -0
- package/src/app/api/workspace/[id]/apps/route.ts +81 -0
- package/src/app/api/workspace/[id]/export/route.ts +67 -0
- package/src/app/api/workspace/[id]/route.ts +168 -0
- package/src/app/api/workspace/auth.ts +34 -0
- package/src/app/api/workspace/config/route.ts +106 -0
- package/src/app/api/workspace/import/route.ts +127 -0
- package/src/app/api/workspace/route.ts +116 -0
- package/src/app/app/page.tsx +2122 -0
- package/src/app/apple-icon.png +0 -0
- package/src/app/docs/page.tsx +178 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +572 -0
- package/src/app/health/page.tsx +5 -0
- package/src/app/hello/page.tsx +15 -0
- package/src/app/icon.png +0 -0
- package/src/app/layout.tsx +34 -0
- package/src/app/page.tsx +986 -0
- package/src/app/providers.tsx +90 -0
- package/src/app/share/[token]/page.tsx +295 -0
- package/src/components/ChainSelector.tsx +144 -0
- package/src/components/HumanActionBar.tsx +695 -0
- package/src/components/NotificationDrawer.tsx +129 -0
- package/src/components/apps/AgentKeysApp.tsx +490 -0
- package/src/components/apps/App.tsx +153 -0
- package/src/components/apps/AppGrid.tsx +15 -0
- package/src/components/apps/DetailedAddressDrawer.tsx +325 -0
- package/src/components/apps/DraggableApp.tsx +562 -0
- package/src/components/apps/IFrameApp.tsx +73 -0
- package/src/components/apps/LogsApp.tsx +360 -0
- package/src/components/apps/SendApp.tsx +394 -0
- package/src/components/apps/SetupWizardApp.tsx +1004 -0
- package/src/components/apps/SystemDefaultsApp.tsx +845 -0
- package/src/components/apps/ThirdPartyApp.tsx +428 -0
- package/src/components/apps/TokenApp.tsx +319 -0
- package/src/components/apps/TransactionsApp.tsx +438 -0
- package/src/components/apps/WalletDetailApp.tsx +1505 -0
- package/src/components/apps/index.ts +13 -0
- package/src/components/design-system/Button.tsx +53 -0
- package/src/components/design-system/ChainIndicator.tsx +65 -0
- package/src/components/design-system/ChainSelector.tsx +137 -0
- package/src/components/design-system/ConfirmationModal.tsx +106 -0
- package/src/components/design-system/ConfirmationPopover.tsx +81 -0
- package/src/components/design-system/Drawer.tsx +123 -0
- package/src/components/design-system/FilterDropdown.tsx +72 -0
- package/src/components/design-system/Modal.tsx +206 -0
- package/src/components/design-system/Popover.tsx +142 -0
- package/src/components/design-system/TextInput.tsx +85 -0
- package/src/components/design-system/Toggle.tsx +58 -0
- package/src/components/design-system/index.ts +11 -0
- package/src/components/docs/DocsThemeToggle.tsx +49 -0
- package/src/components/health/CredentialHealthDashboard.tsx +214 -0
- package/src/components/icons/ChainIcons.tsx +72 -0
- package/src/components/layout/AppStoreDrawer.tsx +369 -0
- package/src/components/layout/ContentArea.tsx +21 -0
- package/src/components/layout/TabBar.tsx +278 -0
- package/src/components/layout/WalletSidebar.tsx +1033 -0
- package/src/components/layout/index.ts +4 -0
- package/src/components/marketing/AuraWalletSpecOverlay.tsx +635 -0
- package/src/components/marketing/DeviceMorphExperience.tsx +216 -0
- package/src/components/vault/ApiKeysConsole.tsx +1080 -0
- package/src/components/vault/AuditConsole.tsx +584 -0
- package/src/components/vault/CredentialDetail.tsx +455 -0
- package/src/components/vault/CredentialEmpty.tsx +55 -0
- package/src/components/vault/CredentialField.tsx +361 -0
- package/src/components/vault/CredentialForm.tsx +1212 -0
- package/src/components/vault/CredentialList.tsx +165 -0
- package/src/components/vault/CredentialRow.tsx +97 -0
- package/src/components/vault/CredentialShareModal.tsx +178 -0
- package/src/components/vault/CredentialVault.tsx +754 -0
- package/src/components/vault/CredentialWalletWidget.tsx +103 -0
- package/src/components/vault/ImportCredentialsModal.tsx +515 -0
- package/src/components/vault/LargeTypeModal.tsx +64 -0
- package/src/components/vault/PasswordGenerator.tsx +224 -0
- package/src/components/vault/TOTPDisplay.tsx +123 -0
- package/src/components/vault/VaultSidebar.tsx +413 -0
- package/src/components/vault/types.ts +54 -0
- package/src/context/AuthContext.tsx +337 -0
- package/src/context/PriceContext.tsx +113 -0
- package/src/context/ThemeContext.tsx +164 -0
- package/src/context/WebSocketContext.tsx +269 -0
- package/src/context/WorkspaceContext.tsx +668 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useAgentActions.ts +368 -0
- package/src/hooks/useBalance.ts +103 -0
- package/src/hooks/useBalances.ts +129 -0
- package/src/instrumentation.ts +12 -0
- package/src/lib/api.ts +449 -0
- package/src/lib/app-loader.ts +148 -0
- package/src/lib/app-registry.ts +178 -0
- package/src/lib/app-sdk.ts +157 -0
- package/src/lib/audit-console-adapter.ts +151 -0
- package/src/lib/auth-client.ts +75 -0
- package/src/lib/config.ts +74 -0
- package/src/lib/crypto.ts +112 -0
- package/src/lib/db.ts +21 -0
- package/src/lib/docs.ts +390 -0
- package/src/lib/events.ts +361 -0
- package/src/lib/pino.ts +24 -0
- package/src/lib/theme-handlers.ts +168 -0
- package/src/lib/theme.ts +351 -0
- package/src/lib/tokenData.ts +378 -0
- package/src/lib/vault-crypto.ts +129 -0
- package/src/lib/websocket-server.ts +302 -0
- package/src/lib/websocket-setup.ts +79 -0
- package/src/lib/wordlist.ts +2050 -0
- package/src/lib/workspace-handlers.ts +285 -0
- package/start.sh +80 -0
- package/tailwind.config.ts +99 -0
- package/tsconfig.json +42 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frontend Crypto Utilities
|
|
3
|
+
*
|
|
4
|
+
* RSA-OAEP encryption for secure password transport.
|
|
5
|
+
* Uses Web Crypto API for encryption with server's public key.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getVaultPublicKeyBase64 } from './vault-crypto';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Convert PEM-encoded public key to ArrayBuffer for Web Crypto API
|
|
12
|
+
*/
|
|
13
|
+
function pemToArrayBuffer(pem: string): ArrayBuffer {
|
|
14
|
+
// Remove PEM headers and newlines
|
|
15
|
+
const b64 = pem
|
|
16
|
+
.replace(/-----BEGIN PUBLIC KEY-----/, '')
|
|
17
|
+
.replace(/-----END PUBLIC KEY-----/, '')
|
|
18
|
+
.replace(/\s/g, '');
|
|
19
|
+
|
|
20
|
+
// Decode base64 to binary
|
|
21
|
+
const binary = atob(b64);
|
|
22
|
+
const bytes = new Uint8Array(binary.length);
|
|
23
|
+
for (let i = 0; i < binary.length; i++) {
|
|
24
|
+
bytes[i] = binary.charCodeAt(i);
|
|
25
|
+
}
|
|
26
|
+
return bytes.buffer;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function arrayBufferToBase64(buffer: ArrayBuffer): string {
|
|
30
|
+
const bytes = new Uint8Array(buffer);
|
|
31
|
+
let binary = '';
|
|
32
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
33
|
+
binary += String.fromCharCode(bytes[i]);
|
|
34
|
+
}
|
|
35
|
+
return btoa(binary);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Encrypt a password using RSA-OAEP with the server's public key
|
|
40
|
+
*
|
|
41
|
+
* @param password - The plaintext password to encrypt
|
|
42
|
+
* @param pemPublicKey - PEM-encoded RSA public key from /auth/connect
|
|
43
|
+
* @returns Base64-encoded encrypted password
|
|
44
|
+
*/
|
|
45
|
+
export async function encryptPassword(
|
|
46
|
+
password: string,
|
|
47
|
+
pemPublicKey: string
|
|
48
|
+
): Promise<string> {
|
|
49
|
+
// Import the PEM public key
|
|
50
|
+
const keyData = pemToArrayBuffer(pemPublicKey);
|
|
51
|
+
const publicKey = await crypto.subtle.importKey(
|
|
52
|
+
'spki',
|
|
53
|
+
keyData,
|
|
54
|
+
{ name: 'RSA-OAEP', hash: 'SHA-256' },
|
|
55
|
+
false,
|
|
56
|
+
['encrypt']
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// Encrypt the password
|
|
60
|
+
const encoded = new TextEncoder().encode(password);
|
|
61
|
+
const encrypted = await crypto.subtle.encrypt(
|
|
62
|
+
{ name: 'RSA-OAEP' },
|
|
63
|
+
publicKey,
|
|
64
|
+
encoded
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// Convert to base64 for transport
|
|
68
|
+
const bytes = new Uint8Array(encrypted);
|
|
69
|
+
let binary = '';
|
|
70
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
71
|
+
binary += String.fromCharCode(bytes[i]);
|
|
72
|
+
}
|
|
73
|
+
return btoa(binary);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let tokenMintPubkeyPromise: Promise<string> | null = null;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Generate (or reuse) an in-memory RSA-OAEP public key for token mint requests.
|
|
80
|
+
*
|
|
81
|
+
* Private key persistence/decryption lifecycle is handled separately by vault UI
|
|
82
|
+
* code (TODO-029). This helper only provides the required pubkey.
|
|
83
|
+
*/
|
|
84
|
+
export async function getTokenMintPubkey(): Promise<string> {
|
|
85
|
+
// Prefer the vault keypair when available (UI unlock flow)
|
|
86
|
+
const vaultPubkey = getVaultPublicKeyBase64();
|
|
87
|
+
if (vaultPubkey) return vaultPubkey;
|
|
88
|
+
|
|
89
|
+
if (!tokenMintPubkeyPromise) {
|
|
90
|
+
tokenMintPubkeyPromise = (async () => {
|
|
91
|
+
const pair = await crypto.subtle.generateKey(
|
|
92
|
+
{
|
|
93
|
+
name: 'RSA-OAEP',
|
|
94
|
+
modulusLength: 2048,
|
|
95
|
+
publicExponent: new Uint8Array([1, 0, 1]),
|
|
96
|
+
hash: 'SHA-256',
|
|
97
|
+
},
|
|
98
|
+
true,
|
|
99
|
+
['encrypt', 'decrypt'],
|
|
100
|
+
);
|
|
101
|
+
const spki = await crypto.subtle.exportKey('spki', pair.publicKey);
|
|
102
|
+
return arrayBufferToBase64(spki);
|
|
103
|
+
})();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
return await tokenMintPubkeyPromise;
|
|
108
|
+
} catch (error) {
|
|
109
|
+
tokenMintPubkeyPromise = null;
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
}
|
package/src/lib/db.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { PrismaClient } from '@prisma/client';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
// Resolve DATABASE_URL to ~/.aurawallet/aurawallet.db
|
|
6
|
+
// Tests and explicit overrides are preserved
|
|
7
|
+
const envUrl = process.env.DATABASE_URL;
|
|
8
|
+
if (!envUrl || envUrl === 'file:./dev.db') {
|
|
9
|
+
const dbPath = path.join(os.homedir(), '.aurawallet', 'aurawallet.db');
|
|
10
|
+
process.env.DATABASE_URL = `file:${dbPath}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const globalForPrisma = globalThis as unknown as {
|
|
14
|
+
prisma: PrismaClient | undefined;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const prisma = globalForPrisma.prisma ?? new PrismaClient();
|
|
18
|
+
|
|
19
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
20
|
+
globalForPrisma.prisma = prisma;
|
|
21
|
+
}
|
package/src/lib/docs.ts
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import 'server-only';
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { marked } from 'marked';
|
|
6
|
+
|
|
7
|
+
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD';
|
|
8
|
+
|
|
9
|
+
const HTTP_METHODS = new Set<HttpMethod>(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD']);
|
|
10
|
+
const DOCS_DIR = path.join(process.cwd(), 'docs');
|
|
11
|
+
const ROOT_README = path.join(process.cwd(), 'README.md');
|
|
12
|
+
const EXCLUDED_DOC_FILES = new Set(['API.md']);
|
|
13
|
+
const ALWAYS_EXCLUDED_TOP_LEVEL_DIRS = new Set(['specs']);
|
|
14
|
+
const TRUTHY_ENV = new Set(['1', 'true', 'yes', 'on']);
|
|
15
|
+
const FALSY_ENV = new Set(['0', 'false', 'no', 'off']);
|
|
16
|
+
|
|
17
|
+
const parseBooleanEnv = (value: string | undefined): boolean | null => {
|
|
18
|
+
if (!value) return null;
|
|
19
|
+
const normalized = value.trim().toLowerCase();
|
|
20
|
+
if (TRUTHY_ENV.has(normalized)) return true;
|
|
21
|
+
if (FALSY_ENV.has(normalized)) return false;
|
|
22
|
+
return null;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const shouldShowInternalDocs = (): boolean => {
|
|
26
|
+
const explicit = parseBooleanEnv(process.env.NEXT_PUBLIC_SHOW_INTERNAL_DOCS);
|
|
27
|
+
if (explicit !== null) return explicit;
|
|
28
|
+
|
|
29
|
+
// In hosted environments, NODE_ENV is "production" for preview/staging too.
|
|
30
|
+
// Prefer VERCEL_ENV when present so preview/dev deploys can still see internal docs.
|
|
31
|
+
const vercelEnv = process.env.VERCEL_ENV?.trim().toLowerCase();
|
|
32
|
+
if (vercelEnv) return vercelEnv !== 'production';
|
|
33
|
+
|
|
34
|
+
return process.env.NODE_ENV !== 'production';
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const SHOW_INTERNAL_DOCS = shouldShowInternalDocs();
|
|
38
|
+
|
|
39
|
+
/** README.md lives at repo root, not in docs/. Map it to a virtual entry. */
|
|
40
|
+
const README_ENTRY = 'README.md';
|
|
41
|
+
const README_SIDEBAR_TITLE = 'Getting Started';
|
|
42
|
+
|
|
43
|
+
export interface DocFile {
|
|
44
|
+
filename: string;
|
|
45
|
+
title: string;
|
|
46
|
+
summary: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface DocGroup {
|
|
50
|
+
label: string;
|
|
51
|
+
docs: DocFile[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Sidebar groupings for public docs. Internal docs are appended in dev only.
|
|
56
|
+
* Public docs not listed here are intentionally hidden until explicitly categorized.
|
|
57
|
+
*/
|
|
58
|
+
const DOC_CATEGORIES: { label: string; filenames: string[] }[] = [
|
|
59
|
+
{ label: 'START HERE', filenames: ['README.md', 'JOBS.md', 'SETUP.md'] },
|
|
60
|
+
{ label: 'CREDENTIAL VAULT', filenames: ['credentials.md', 'passkeys.md', 'totp.md', 'oauth2.md', 'aura-file.md'] },
|
|
61
|
+
{ label: 'PLATFORM & SECURITY', filenames: ['ARCHITECTURE.md', 'security.md', 'AUTH.md', 'BEST-PRACTICES.md', 'WORKSPACE.md'] },
|
|
62
|
+
{ label: 'APPS & TOOLS', filenames: ['APPS.md', 'DEVELOPING-APPS.md', 'EXTENSION.md', 'MCP.md', 'CLI.md', 'ADAPTERS.md', 'agent-auth.md', 'PROTOCOL.md'] },
|
|
63
|
+
{ label: 'WALLET (LEGACY)', filenames: ['wallet/README.md', 'wallet/STRATEGY.md', 'wallet/DEVELOPING-STRATEGIES.md', 'wallet/AI.md'] },
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
export interface ApiEndpoint {
|
|
67
|
+
method: HttpMethod;
|
|
68
|
+
path: string;
|
|
69
|
+
section: string;
|
|
70
|
+
source: 'http-fence' | 'table' | 'inline';
|
|
71
|
+
permission?: string;
|
|
72
|
+
authentication?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface EndpointCandidate extends ApiEndpoint {
|
|
76
|
+
lineIndex: number;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const stripMarkdown = (value: string) => value.replace(/[`*_]/g, '').trim();
|
|
80
|
+
|
|
81
|
+
const cleanPath = (value: string): string | null => {
|
|
82
|
+
const normalized = value.trim().replace(/^`|`$/g, '').replace(/[),.;]+$/, '');
|
|
83
|
+
if (!normalized.startsWith('/')) return null;
|
|
84
|
+
return normalized;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const parseSummary = (content: string): string => {
|
|
88
|
+
const lines = content.split(/\r?\n/);
|
|
89
|
+
for (const rawLine of lines) {
|
|
90
|
+
const line = rawLine.trim();
|
|
91
|
+
if (!line) continue;
|
|
92
|
+
if (line.startsWith('#')) continue;
|
|
93
|
+
if (line.startsWith('```')) continue;
|
|
94
|
+
if (line.startsWith('|')) continue;
|
|
95
|
+
if (line === '---') continue;
|
|
96
|
+
return stripMarkdown(line);
|
|
97
|
+
}
|
|
98
|
+
return 'No summary available.';
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const parseTitle = (filename: string, content: string): string => {
|
|
102
|
+
const headingMatch = content.match(/^#\s+(.+)$/m);
|
|
103
|
+
if (headingMatch) return stripMarkdown(headingMatch[1]);
|
|
104
|
+
return path.basename(filename).replace(/\.md$/i, '').toUpperCase();
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const normalizeDocPath = (value: string): string =>
|
|
108
|
+
path.posix.normalize(value.replace(/\\/g, '/').replace(/^\/+/, ''));
|
|
109
|
+
|
|
110
|
+
const isTopLevelDirExcluded = (topLevelDir?: string): boolean => {
|
|
111
|
+
if (!topLevelDir) return false;
|
|
112
|
+
if (ALWAYS_EXCLUDED_TOP_LEVEL_DIRS.has(topLevelDir)) return true;
|
|
113
|
+
if (!SHOW_INTERNAL_DOCS && topLevelDir === 'internal') return true;
|
|
114
|
+
return false;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const isExcludedDocPath = (docPath: string): boolean => {
|
|
118
|
+
const normalized = normalizeDocPath(docPath);
|
|
119
|
+
if (normalized === 'API.md') return true;
|
|
120
|
+
const [topLevel] = normalized.split('/');
|
|
121
|
+
if (isTopLevelDirExcluded(topLevel)) return true;
|
|
122
|
+
return false;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const collectPublicDocFiles = async (dirPath: string, prefix = ''): Promise<string[]> => {
|
|
126
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
127
|
+
const sorted = [...entries].sort((a, b) => a.name.localeCompare(b.name));
|
|
128
|
+
const collected: string[] = [];
|
|
129
|
+
|
|
130
|
+
for (const entry of sorted) {
|
|
131
|
+
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
132
|
+
if (entry.isDirectory()) {
|
|
133
|
+
if (!prefix && isTopLevelDirExcluded(entry.name)) continue;
|
|
134
|
+
collected.push(...await collectPublicDocFiles(path.join(dirPath, entry.name), relativePath));
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (!entry.isFile()) continue;
|
|
138
|
+
if (!entry.name.toLowerCase().endsWith('.md')) continue;
|
|
139
|
+
if (isExcludedDocPath(relativePath)) continue;
|
|
140
|
+
if (EXCLUDED_DOC_FILES.has(path.basename(relativePath))) continue;
|
|
141
|
+
collected.push(relativePath);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return collected;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const parseEndpointMeta = (lines: string[], lineIndex: number): Pick<ApiEndpoint, 'permission' | 'authentication'> => {
|
|
148
|
+
const lookahead = lines.slice(lineIndex + 1, lineIndex + 10).join('\n');
|
|
149
|
+
const permissionMatch = lookahead.match(/Permission:\s*`([^`]+)`/i);
|
|
150
|
+
const authenticationMatch = lookahead.match(/Authentication:\s*([^\n]+)/i);
|
|
151
|
+
const publicMatch = lookahead.match(/no authentication required/i);
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
permission: permissionMatch ? stripMarkdown(permissionMatch[1]) : undefined,
|
|
155
|
+
authentication: publicMatch
|
|
156
|
+
? 'Public (no auth)'
|
|
157
|
+
: authenticationMatch
|
|
158
|
+
? stripMarkdown(authenticationMatch[1])
|
|
159
|
+
: undefined,
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const maybePushCandidate = (
|
|
164
|
+
candidates: EndpointCandidate[],
|
|
165
|
+
params: Omit<EndpointCandidate, 'permission' | 'authentication'>,
|
|
166
|
+
lines: string[],
|
|
167
|
+
) => {
|
|
168
|
+
const pathValue = cleanPath(params.path);
|
|
169
|
+
if (!pathValue) return;
|
|
170
|
+
if (!HTTP_METHODS.has(params.method)) return;
|
|
171
|
+
const meta = parseEndpointMeta(lines, params.lineIndex);
|
|
172
|
+
candidates.push({ ...params, path: pathValue, ...meta });
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export const listDocFiles = async (): Promise<DocFile[]> => {
|
|
176
|
+
const markdownFiles = (await collectPublicDocFiles(DOCS_DIR))
|
|
177
|
+
.sort((a, b) => {
|
|
178
|
+
const order: Record<string, number> = { 'SETUP.md': 0, 'wallet/README.md': 1 };
|
|
179
|
+
const aPriority = order[a] ?? 999;
|
|
180
|
+
const bPriority = order[b] ?? 999;
|
|
181
|
+
if (aPriority !== bPriority) return aPriority - bPriority;
|
|
182
|
+
return a.localeCompare(b);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Root README.md goes first as "Getting Started"
|
|
186
|
+
const readmeContent = await fs.readFile(ROOT_README, 'utf8').catch(() => '');
|
|
187
|
+
const readmeEntry: DocFile = {
|
|
188
|
+
filename: README_ENTRY,
|
|
189
|
+
title: README_SIDEBAR_TITLE,
|
|
190
|
+
summary: readmeContent ? parseSummary(readmeContent) : 'Getting started with AuraWallet.',
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const docs = await Promise.all(
|
|
194
|
+
markdownFiles.map(async (filename) => {
|
|
195
|
+
const fullPath = path.join(DOCS_DIR, filename);
|
|
196
|
+
const content = await fs.readFile(fullPath, 'utf8');
|
|
197
|
+
return {
|
|
198
|
+
filename,
|
|
199
|
+
title: parseTitle(filename, content),
|
|
200
|
+
summary: parseSummary(content),
|
|
201
|
+
};
|
|
202
|
+
}),
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
return [readmeEntry, ...docs];
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
export const listDocGroups = async (): Promise<DocGroup[]> => {
|
|
209
|
+
const allDocs = await listDocFiles();
|
|
210
|
+
const docMap = new Map(allDocs.map((doc) => [doc.filename, doc]));
|
|
211
|
+
|
|
212
|
+
const groups: DocGroup[] = DOC_CATEGORIES.map(({ label, filenames }) => ({
|
|
213
|
+
label,
|
|
214
|
+
docs: filenames
|
|
215
|
+
.filter((f) => docMap.has(f))
|
|
216
|
+
.map((f) => docMap.get(f)!),
|
|
217
|
+
})).filter((g) => g.docs.length > 0);
|
|
218
|
+
|
|
219
|
+
if (SHOW_INTERNAL_DOCS) {
|
|
220
|
+
const internalDocs = allDocs
|
|
221
|
+
.filter((doc) => doc.filename.startsWith('internal/'))
|
|
222
|
+
.sort((a, b) => a.filename.localeCompare(b.filename));
|
|
223
|
+
if (internalDocs.length > 0) {
|
|
224
|
+
groups.push({ label: 'INTERNAL (DEV ONLY)', docs: internalDocs });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return groups;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
export const readDocFile = async (filename: string): Promise<string> => {
|
|
232
|
+
const normalized = normalizeDocPath(filename);
|
|
233
|
+
if (!normalized.toLowerCase().endsWith('.md')) {
|
|
234
|
+
throw new Error(`Invalid doc file: ${filename}`);
|
|
235
|
+
}
|
|
236
|
+
// Root README.md is served from repo root, not docs/
|
|
237
|
+
if (normalized === README_ENTRY) {
|
|
238
|
+
return fs.readFile(ROOT_README, 'utf8');
|
|
239
|
+
}
|
|
240
|
+
if (normalized === 'API.md') {
|
|
241
|
+
return fs.readFile(path.join(DOCS_DIR, 'API.md'), 'utf8');
|
|
242
|
+
}
|
|
243
|
+
if (normalized.startsWith('../') || normalized === '..') {
|
|
244
|
+
throw new Error('Path traversal attempt blocked');
|
|
245
|
+
}
|
|
246
|
+
if (isExcludedDocPath(normalized)) {
|
|
247
|
+
throw new Error(`Doc is excluded from /docs: ${filename}`);
|
|
248
|
+
}
|
|
249
|
+
const fullPath = path.join(DOCS_DIR, normalized);
|
|
250
|
+
if (!fullPath.startsWith(DOCS_DIR)) {
|
|
251
|
+
throw new Error('Path traversal attempt blocked');
|
|
252
|
+
}
|
|
253
|
+
return fs.readFile(fullPath, 'utf8');
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
export const parseApiEndpoints = (content: string): ApiEndpoint[] => {
|
|
257
|
+
const lines = content.split(/\r?\n/);
|
|
258
|
+
let currentH2 = 'General';
|
|
259
|
+
let currentH3 = '';
|
|
260
|
+
let inCodeBlock = false;
|
|
261
|
+
let codeFenceLang = '';
|
|
262
|
+
const candidates: EndpointCandidate[] = [];
|
|
263
|
+
|
|
264
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
265
|
+
const line = lines[index];
|
|
266
|
+
const trim = line.trim();
|
|
267
|
+
|
|
268
|
+
if (trim.startsWith('```')) {
|
|
269
|
+
if (!inCodeBlock) {
|
|
270
|
+
inCodeBlock = true;
|
|
271
|
+
codeFenceLang = trim.slice(3).trim().toLowerCase();
|
|
272
|
+
} else {
|
|
273
|
+
inCodeBlock = false;
|
|
274
|
+
codeFenceLang = '';
|
|
275
|
+
}
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (!inCodeBlock) {
|
|
280
|
+
const h2Match = trim.match(/^##\s+(.+)$/);
|
|
281
|
+
if (h2Match) {
|
|
282
|
+
currentH2 = stripMarkdown(h2Match[1]);
|
|
283
|
+
currentH3 = '';
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
const h3Match = trim.match(/^###\s+(.+)$/);
|
|
287
|
+
if (h3Match) {
|
|
288
|
+
currentH3 = stripMarkdown(h3Match[1]);
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const section = currentH3 ? `${currentH2} / ${currentH3}` : currentH2;
|
|
294
|
+
|
|
295
|
+
if (inCodeBlock && ['', 'http', 'bash', 'sh', 'shell', 'text'].includes(codeFenceLang)) {
|
|
296
|
+
const methodPath = trim.match(/^(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\s+(`?)(\/[^\s`]+)\2$/i);
|
|
297
|
+
if (methodPath) {
|
|
298
|
+
maybePushCandidate(
|
|
299
|
+
candidates,
|
|
300
|
+
{
|
|
301
|
+
method: methodPath[1].toUpperCase() as HttpMethod,
|
|
302
|
+
path: methodPath[3],
|
|
303
|
+
section,
|
|
304
|
+
source: 'http-fence',
|
|
305
|
+
lineIndex: index,
|
|
306
|
+
},
|
|
307
|
+
lines,
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (!inCodeBlock && trim.startsWith('|')) {
|
|
313
|
+
const columns = trim.split('|').map((value) => value.trim());
|
|
314
|
+
if (columns.length >= 4) {
|
|
315
|
+
const methodCell = columns[1].toUpperCase();
|
|
316
|
+
const pathCell = columns[2];
|
|
317
|
+
if (HTTP_METHODS.has(methodCell as HttpMethod)) {
|
|
318
|
+
maybePushCandidate(
|
|
319
|
+
candidates,
|
|
320
|
+
{
|
|
321
|
+
method: methodCell as HttpMethod,
|
|
322
|
+
path: pathCell,
|
|
323
|
+
section,
|
|
324
|
+
source: 'table',
|
|
325
|
+
lineIndex: index,
|
|
326
|
+
},
|
|
327
|
+
lines,
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (!inCodeBlock) {
|
|
334
|
+
for (const match of trim.matchAll(/\b(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\s+`?(\/[A-Za-z0-9:._?=&/%\-]+)`?/gi)) {
|
|
335
|
+
maybePushCandidate(
|
|
336
|
+
candidates,
|
|
337
|
+
{
|
|
338
|
+
method: match[1].toUpperCase() as HttpMethod,
|
|
339
|
+
path: match[2],
|
|
340
|
+
section,
|
|
341
|
+
source: 'inline',
|
|
342
|
+
lineIndex: index,
|
|
343
|
+
},
|
|
344
|
+
lines,
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const deduped = new Map<string, ApiEndpoint>();
|
|
351
|
+
for (const candidate of candidates) {
|
|
352
|
+
const key = `${candidate.method} ${candidate.path}`;
|
|
353
|
+
const existing = deduped.get(key);
|
|
354
|
+
if (!existing) {
|
|
355
|
+
const endpoint: ApiEndpoint = {
|
|
356
|
+
method: candidate.method,
|
|
357
|
+
path: candidate.path,
|
|
358
|
+
section: candidate.section,
|
|
359
|
+
source: candidate.source,
|
|
360
|
+
permission: candidate.permission,
|
|
361
|
+
authentication: candidate.authentication,
|
|
362
|
+
};
|
|
363
|
+
deduped.set(key, endpoint);
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
if (!existing.permission && candidate.permission) existing.permission = candidate.permission;
|
|
367
|
+
if (!existing.authentication && candidate.authentication) existing.authentication = candidate.authentication;
|
|
368
|
+
if (existing.section === 'General' && candidate.section !== 'General') existing.section = candidate.section;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return [...deduped.values()].sort((a, b) => {
|
|
372
|
+
const sectionCompare = a.section.localeCompare(b.section);
|
|
373
|
+
if (sectionCompare !== 0) return sectionCompare;
|
|
374
|
+
const pathCompare = a.path.localeCompare(b.path);
|
|
375
|
+
if (pathCompare !== 0) return pathCompare;
|
|
376
|
+
return a.method.localeCompare(b.method);
|
|
377
|
+
});
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
export const slugify = (text: string): string =>
|
|
381
|
+
text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
382
|
+
|
|
383
|
+
export const renderMarkdown = (content: string): string => {
|
|
384
|
+
const renderer = new marked.Renderer();
|
|
385
|
+
renderer.heading = ({ text, depth }) => {
|
|
386
|
+
const id = slugify(text.replace(/<[^>]*>/g, ''));
|
|
387
|
+
return `<h${depth} id="${id}">${text}</h${depth}>`;
|
|
388
|
+
};
|
|
389
|
+
return marked.parse(content, { async: false, renderer }) as string;
|
|
390
|
+
};
|