refactor(MAKEFILE): migrate to Vite and remove sqlc dependency
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,3 +1,7 @@
|
||||
example/node_modules
|
||||
build
|
||||
example/enclave.wasm
|
||||
src/dist
|
||||
src/node_modules
|
||||
dist
|
||||
node_modules
|
||||
|
||||
98
Makefile
98
Makefile
@@ -1,84 +1,72 @@
|
||||
.PHONY: all generate build build-debug build-opt test test-cover test-plugin lint fmt vet clean tidy deps verify serve help
|
||||
.PHONY: start deps build sdk dev test test-plugin lint fmt clean help
|
||||
|
||||
MODULE := enclave
|
||||
BINARY := enclave.wasm
|
||||
BUILD_DIR := example
|
||||
|
||||
all: generate build
|
||||
# === Primary Commands ===
|
||||
|
||||
generate:
|
||||
@sqlc generate
|
||||
start: deps build sdk dev
|
||||
|
||||
deps:
|
||||
@command -v sqlc >/dev/null || go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
|
||||
@command -v golangci-lint >/dev/null || go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||
@bun install
|
||||
@cd example && bun install
|
||||
|
||||
build:
|
||||
@echo "Building WASM plugin..."
|
||||
@GOOS=wasip1 GOARCH=wasm go build -o $(BUILD_DIR)/$(BINARY) .
|
||||
@echo "Built $(BUILD_DIR)/$(BINARY)"
|
||||
|
||||
build-debug:
|
||||
@GOOS=wasip1 GOARCH=wasm go build -gcflags="all=-N -l" -o $(BUILD_DIR)/$(BINARY) .
|
||||
sdk:
|
||||
@echo "Building TypeScript SDK..."
|
||||
@bun run build
|
||||
@echo "Built dist/enclave.js"
|
||||
|
||||
build-opt:
|
||||
@GOOS=wasip1 GOARCH=wasm go build -ldflags="-s -w" -o $(BUILD_DIR)/$(BINARY) .
|
||||
@wasm-opt -Os $(BUILD_DIR)/$(BINARY) -o $(BUILD_DIR)/$(BINARY)
|
||||
dev:
|
||||
@echo "Starting dev server at http://localhost:8080"
|
||||
@cd example && bun run dev
|
||||
|
||||
# === Testing ===
|
||||
|
||||
test:
|
||||
@go test -v ./...
|
||||
|
||||
test-cover:
|
||||
@go test -coverprofile=coverage.out ./...
|
||||
@go tool cover -html=coverage.out -o coverage.html
|
||||
test-plugin: build
|
||||
@echo "Testing generate()..."
|
||||
@extism call $(BUILD_DIR)/$(BINARY) generate --input '{"credential":"dGVzdC1jcmVkZW50aWFs"}' --wasi
|
||||
@echo "\nTesting query()..."
|
||||
@extism call $(BUILD_DIR)/$(BINARY) query --input '{"did":""}' --wasi
|
||||
|
||||
test-plugin:
|
||||
@extism call $(BUILD_DIR)/$(BINARY) generate --input '{"credential":"dGVzdA=="}' --wasi
|
||||
test-sdk: sdk
|
||||
@cd example && bun run test
|
||||
|
||||
serve: build
|
||||
@echo "Starting Vite dev server at http://localhost:8080"
|
||||
@cd example && npm run dev
|
||||
# === Code Quality ===
|
||||
|
||||
lint:
|
||||
@golangci-lint run ./...
|
||||
|
||||
fmt:
|
||||
@go fmt ./...
|
||||
@gofumpt -w .
|
||||
@bun run --filter '*' format 2>/dev/null || true
|
||||
|
||||
vet:
|
||||
@go vet ./...
|
||||
generate:
|
||||
@sqlc generate
|
||||
|
||||
# === Utilities ===
|
||||
|
||||
clean:
|
||||
@rm -f $(BUILD_DIR)/$(BINARY)
|
||||
@rm -f coverage.out coverage.html
|
||||
|
||||
tidy:
|
||||
@go mod tidy
|
||||
|
||||
deps:
|
||||
@go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
|
||||
@go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||
@go install mvdan.cc/gofumpt@latest
|
||||
@cd example && npm install
|
||||
@echo "Install Extism CLI: https://extism.org/docs/install"
|
||||
|
||||
verify: fmt vet lint test
|
||||
@rm -rf dist
|
||||
|
||||
help:
|
||||
@echo "Motr Enclave - Extism Plugin (Go 1.25/wasip1)"
|
||||
@echo "Motr Enclave"
|
||||
@echo ""
|
||||
@echo "Build targets:"
|
||||
@echo " build - Build WASM plugin for wasip1"
|
||||
@echo " build-debug - Build with debug symbols"
|
||||
@echo " build-opt - Build optimized (requires wasm-opt)"
|
||||
@echo ""
|
||||
@echo "Development targets:"
|
||||
@echo " generate - Run sqlc to generate Go code"
|
||||
@echo " test - Run tests"
|
||||
@echo " test-cover - Run tests with coverage"
|
||||
@echo " test-plugin - Test plugin with Extism CLI"
|
||||
@echo " serve - Build and serve example/ for browser testing"
|
||||
@echo " lint - Run golangci-lint"
|
||||
@echo " fmt - Format code"
|
||||
@echo " vet - Run go vet"
|
||||
@echo " verify - Run fmt, vet, lint, and test"
|
||||
@echo ""
|
||||
@echo "Utility targets:"
|
||||
@echo " clean - Remove build artifacts"
|
||||
@echo " tidy - Run go mod tidy"
|
||||
@echo " deps - Install development dependencies"
|
||||
@echo " make start - Full setup + dev server (recommended)"
|
||||
@echo " make build - Build WASM plugin"
|
||||
@echo " make sdk - Build TypeScript SDK"
|
||||
@echo " make dev - Start dev server"
|
||||
@echo " make test - Run Go tests"
|
||||
@echo " make test-plugin - Test plugin with Extism CLI"
|
||||
@echo " make test-sdk - Run SDK tests in browser"
|
||||
@echo " make clean - Remove build artifacts"
|
||||
|
||||
@@ -1,168 +1,81 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Motr Enclave Test</title>
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
}
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
.card h2 {
|
||||
margin-top: 0;
|
||||
color: #555;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
}
|
||||
input, textarea {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
font-size: 14px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
textarea {
|
||||
min-height: 100px;
|
||||
resize: vertical;
|
||||
}
|
||||
button {
|
||||
background: #4a90d9;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
button:hover {
|
||||
background: #357abd;
|
||||
}
|
||||
button:disabled {
|
||||
background: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.output {
|
||||
background: #1e1e1e;
|
||||
color: #d4d4d4;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
font-size: 13px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.status {
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.status.success {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
.status.error {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
.status.loading {
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
}
|
||||
.btn-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Motr Enclave</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: system-ui, sans-serif; background: #0a0a0a; color: #e5e5e5; padding: 2rem; }
|
||||
h1 { font-size: 1.5rem; margin-bottom: 1rem; color: #fff; }
|
||||
.container { max-width: 800px; margin: 0 auto; }
|
||||
.card { background: #171717; border-radius: 8px; padding: 1rem; margin-bottom: 1rem; }
|
||||
.card h2 { font-size: 0.875rem; color: #a3a3a3; margin-bottom: 0.5rem; font-weight: 500; }
|
||||
.status { display: inline-block; padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.75rem; }
|
||||
.status.ok { background: #14532d; color: #4ade80; }
|
||||
.status.err { background: #7f1d1d; color: #f87171; }
|
||||
.status.wait { background: #422006; color: #fbbf24; }
|
||||
button { background: #2563eb; color: #fff; border: none; padding: 0.5rem 1rem; border-radius: 4px; cursor: pointer; font-size: 0.875rem; margin-right: 0.5rem; margin-bottom: 0.5rem; }
|
||||
button:hover { background: #1d4ed8; }
|
||||
button:disabled { background: #374151; cursor: not-allowed; }
|
||||
input { width: 100%; background: #262626; border: 1px solid #404040; color: #fff; padding: 0.5rem; border-radius: 4px; font-family: monospace; font-size: 0.875rem; margin-bottom: 0.5rem; }
|
||||
.log { background: #0a0a0a; border: 1px solid #262626; border-radius: 4px; padding: 0.75rem; font-family: monospace; font-size: 0.75rem; max-height: 300px; overflow-y: auto; white-space: pre-wrap; }
|
||||
.log-entry { padding: 0.125rem 0; border-bottom: 1px solid #1a1a1a; }
|
||||
.log-entry:last-child { border-bottom: none; }
|
||||
.log-time { color: #525252; }
|
||||
.log-info { color: #60a5fa; }
|
||||
.log-ok { color: #4ade80; }
|
||||
.log-err { color: #f87171; }
|
||||
.log-data { color: #a78bfa; }
|
||||
.actions { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 0.5rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Motr Enclave Plugin Test</h1>
|
||||
<div class="container">
|
||||
<h1>Motr Enclave</h1>
|
||||
|
||||
<div class="card">
|
||||
<h2>Plugin Status</h2>
|
||||
<div id="status" class="status loading">Loading plugin...</div>
|
||||
<button id="loadPluginBtn" onclick="loadPlugin()">Reload Plugin</button>
|
||||
<h2>Status</h2>
|
||||
<span id="status" class="status wait">Loading...</span>
|
||||
<button onclick="runAllTests()" style="margin-left: 1rem;">Run All Tests</button>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>generate()</h2>
|
||||
<p>Initialize database with WebAuthn credential</p>
|
||||
<label for="credentialInput">Credential (Base64):</label>
|
||||
<input type="text" id="credentialInput" value="dGVzdC1jcmVkZW50aWFsLWRhdGEtZm9yLXRlc3Rpbmc=" placeholder="Base64-encoded PublicKeyCredential">
|
||||
<button onclick="testGenerate()">Run generate()</button>
|
||||
<div id="generateOutput" class="output"></div>
|
||||
<h2>generate(credential)</h2>
|
||||
<input type="text" id="credential" value="dGVzdC1jcmVkZW50aWFs" placeholder="Base64 credential">
|
||||
<button onclick="testGenerate()">Run</button>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>load()</h2>
|
||||
<p>Load database from serialized buffer</p>
|
||||
<label for="databaseInput">Database Buffer (Base64):</label>
|
||||
<input type="text" id="databaseInput" placeholder="Base64-encoded database buffer">
|
||||
<button onclick="testLoad()">Run load()</button>
|
||||
<button onclick="useGeneratedDb()">Use Generated DB</button>
|
||||
<div id="loadOutput" class="output"></div>
|
||||
<h2>load(database)</h2>
|
||||
<input type="text" id="database" placeholder="Base64 database (auto-filled after generate)">
|
||||
<button onclick="testLoad()">Run</button>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>exec()</h2>
|
||||
<p>Execute action with GitHub-style filter syntax</p>
|
||||
<label for="filterInput">Filter:</label>
|
||||
<input type="text" id="filterInput" value="resource:accounts action:list" placeholder="resource:accounts action:sign subject:did:sonr:abc">
|
||||
<label for="tokenInput">UCAN Token (optional):</label>
|
||||
<input type="text" id="tokenInput" placeholder="UCAN token for authorization">
|
||||
<div class="btn-group">
|
||||
<button onclick="testExec()">Run exec()</button>
|
||||
<button onclick="setFilter('resource:accounts action:list')">List Accounts</button>
|
||||
<button onclick="setFilter('resource:credentials action:list')">List Credentials</button>
|
||||
<button onclick="setFilter('resource:sessions action:list')">List Sessions</button>
|
||||
<button onclick="setFilter('resource:accounts action:sign')">Sign</button>
|
||||
</div>
|
||||
<div id="execOutput" class="output"></div>
|
||||
<h2>exec(filter)</h2>
|
||||
<input type="text" id="filter" value="resource:accounts action:list" placeholder="resource:X action:Y">
|
||||
<div class="actions">
|
||||
<button onclick="testExec()">Run</button>
|
||||
<button onclick="setFilter('resource:accounts action:list')">Accounts</button>
|
||||
<button onclick="setFilter('resource:credentials action:list')">Credentials</button>
|
||||
<button onclick="setFilter('resource:sessions action:list')">Sessions</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>query()</h2>
|
||||
<p>Resolve DID to document with resources</p>
|
||||
<label for="didInput">DID:</label>
|
||||
<input type="text" id="didInput" placeholder="did:sonr:abc123 (leave empty for current DID)">
|
||||
<button onclick="testQuery()">Run query()</button>
|
||||
<div id="queryOutput" class="output"></div>
|
||||
<h2>query(did)</h2>
|
||||
<input type="text" id="did" placeholder="did:sonr:... (empty = current)">
|
||||
<button onclick="testQuery()">Run</button>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Console Log</h2>
|
||||
<button onclick="clearLog()">Clear</button>
|
||||
<div id="consoleLog" class="output"></div>
|
||||
<h2>Log</h2>
|
||||
<button onclick="clearLog()" style="margin-bottom: 0.5rem;">Clear</button>
|
||||
<div id="log" class="log"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="test.js"></script>
|
||||
<script type="module" src="./main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
"test": "vite --port 8081 & sleep 2 && node test.runner.js; kill %1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@extism/extism": "^2.0.0-rc13"
|
||||
|
||||
205
example/test.js
205
example/test.js
@@ -1,205 +0,0 @@
|
||||
import createPlugin from '@extism/extism';
|
||||
|
||||
let plugin = null;
|
||||
let generatedDatabase = null;
|
||||
|
||||
function log(message, type = 'info') {
|
||||
const consoleLog = document.getElementById('consoleLog');
|
||||
const timestamp = new Date().toISOString().split('T')[1].split('.')[0];
|
||||
const prefix = type === 'error' ? '[ERROR]' : type === 'success' ? '[OK]' : '[INFO]';
|
||||
consoleLog.textContent += `${timestamp} ${prefix} ${message}\n`;
|
||||
consoleLog.scrollTop = consoleLog.scrollHeight;
|
||||
console[type === 'error' ? 'error' : 'log'](message);
|
||||
}
|
||||
|
||||
function setStatus(message, type) {
|
||||
const status = document.getElementById('status');
|
||||
status.textContent = message;
|
||||
status.className = `status ${type}`;
|
||||
}
|
||||
|
||||
function formatOutput(data) {
|
||||
try {
|
||||
if (typeof data === 'string') {
|
||||
const parsed = JSON.parse(data);
|
||||
return JSON.stringify(parsed, null, 2);
|
||||
}
|
||||
return JSON.stringify(data, null, 2);
|
||||
} catch {
|
||||
return String(data);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPlugin() {
|
||||
setStatus('Loading plugin...', 'loading');
|
||||
log('Loading enclave.wasm...');
|
||||
|
||||
try {
|
||||
const wasmUrl = new URL('./enclave.wasm', window.location.href).href;
|
||||
log(`WASM URL: ${wasmUrl}`);
|
||||
|
||||
const manifest = {
|
||||
wasm: [{ url: wasmUrl }]
|
||||
};
|
||||
|
||||
plugin = await createPlugin(manifest, {
|
||||
useWasi: true,
|
||||
logger: console
|
||||
});
|
||||
|
||||
setStatus('Plugin loaded successfully', 'success');
|
||||
log('Plugin loaded successfully', 'success');
|
||||
} catch (error) {
|
||||
setStatus(`Failed to load plugin: ${error.message}`, 'error');
|
||||
log(`Failed to load plugin: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function testGenerate() {
|
||||
if (!plugin) {
|
||||
log('Plugin not loaded', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const output = document.getElementById('generateOutput');
|
||||
const credential = document.getElementById('credentialInput').value;
|
||||
|
||||
log(`Calling generate() with credential: ${credential.substring(0, 20)}...`);
|
||||
output.textContent = 'Running...';
|
||||
|
||||
try {
|
||||
const input = JSON.stringify({ credential });
|
||||
const result = await plugin.call('generate', input);
|
||||
const data = result.json();
|
||||
|
||||
output.textContent = formatOutput(data);
|
||||
log(`generate() completed. DID: ${data.did}`, 'success');
|
||||
|
||||
if (data.database) {
|
||||
generatedDatabase = data.database;
|
||||
log('Database buffer stored for load() test');
|
||||
}
|
||||
} catch (error) {
|
||||
output.textContent = `Error: ${error.message}`;
|
||||
log(`generate() failed: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function testLoad() {
|
||||
if (!plugin) {
|
||||
log('Plugin not loaded', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const output = document.getElementById('loadOutput');
|
||||
const databaseInput = document.getElementById('databaseInput').value;
|
||||
|
||||
if (!databaseInput) {
|
||||
output.textContent = 'Error: Database buffer is required';
|
||||
log('load() requires database buffer', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
log('Calling load()...');
|
||||
output.textContent = 'Running...';
|
||||
|
||||
try {
|
||||
const input = JSON.stringify({
|
||||
database: Array.from(atob(databaseInput), c => c.charCodeAt(0))
|
||||
});
|
||||
const result = await plugin.call('load', input);
|
||||
const data = result.json();
|
||||
|
||||
output.textContent = formatOutput(data);
|
||||
log(`load() completed. Success: ${data.success}`, data.success ? 'success' : 'error');
|
||||
} catch (error) {
|
||||
output.textContent = `Error: ${error.message}`;
|
||||
log(`load() failed: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function useGeneratedDb() {
|
||||
if (generatedDatabase) {
|
||||
const base64 = btoa(String.fromCharCode(...generatedDatabase));
|
||||
document.getElementById('databaseInput').value = base64;
|
||||
log('Populated database input with generated database');
|
||||
} else {
|
||||
log('No generated database available. Run generate() first.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function testExec() {
|
||||
if (!plugin) {
|
||||
log('Plugin not loaded', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const output = document.getElementById('execOutput');
|
||||
const filter = document.getElementById('filterInput').value;
|
||||
const token = document.getElementById('tokenInput').value;
|
||||
|
||||
if (!filter) {
|
||||
output.textContent = 'Error: Filter is required';
|
||||
log('exec() requires filter', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
log(`Calling exec() with filter: ${filter}`);
|
||||
output.textContent = 'Running...';
|
||||
|
||||
try {
|
||||
const input = JSON.stringify({ filter, token: token || undefined });
|
||||
const result = await plugin.call('exec', input);
|
||||
const data = result.json();
|
||||
|
||||
output.textContent = formatOutput(data);
|
||||
log(`exec() completed. Success: ${data.success}`, data.success ? 'success' : 'error');
|
||||
} catch (error) {
|
||||
output.textContent = `Error: ${error.message}`;
|
||||
log(`exec() failed: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function setFilter(filter) {
|
||||
document.getElementById('filterInput').value = filter;
|
||||
}
|
||||
|
||||
async function testQuery() {
|
||||
if (!plugin) {
|
||||
log('Plugin not loaded', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const output = document.getElementById('queryOutput');
|
||||
const did = document.getElementById('didInput').value;
|
||||
|
||||
log(`Calling query() with DID: ${did || '(current)'}`);
|
||||
output.textContent = 'Running...';
|
||||
|
||||
try {
|
||||
const input = JSON.stringify({ did: did || '' });
|
||||
const result = await plugin.call('query', input);
|
||||
const data = result.json();
|
||||
|
||||
output.textContent = formatOutput(data);
|
||||
log(`query() completed. DID: ${data.did}`, 'success');
|
||||
} catch (error) {
|
||||
output.textContent = `Error: ${error.message}`;
|
||||
log(`query() failed: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function clearLog() {
|
||||
document.getElementById('consoleLog').textContent = '';
|
||||
}
|
||||
|
||||
window.loadPlugin = loadPlugin;
|
||||
window.testGenerate = testGenerate;
|
||||
window.testLoad = testLoad;
|
||||
window.useGeneratedDb = useGeneratedDb;
|
||||
window.testExec = testExec;
|
||||
window.setFilter = setFilter;
|
||||
window.testQuery = testQuery;
|
||||
window.clearLog = clearLog;
|
||||
|
||||
loadPlugin();
|
||||
Reference in New Issue
Block a user