@rankcli/cli 0.0.21 → 0.0.22
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/dist/chunk-YX67Q5F6.mjs +162 -0
- package/dist/index.js +170 -12
- package/dist/index.mjs +30 -6
- package/dist/wasm-engine.d.mts +28 -40
- package/dist/wasm-engine.d.ts +28 -40
- package/dist/wasm-engine.js +53 -65
- package/dist/wasm-engine.mjs +7 -162
- package/package.json +5 -2
- package/dist/chunk-Y6FXYEAI.mjs +0 -10
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/wasm-engine.ts
|
|
9
|
+
import * as vm from "vm";
|
|
10
|
+
import * as path from "path";
|
|
11
|
+
import * as fs from "fs";
|
|
12
|
+
var PREMIUM_CHECK_NAMES = [
|
|
13
|
+
"analyzeFeaturedSnippet",
|
|
14
|
+
"analyzeInternalLinkGraph",
|
|
15
|
+
"analyzeEEATSignals",
|
|
16
|
+
"analyzeContentScience",
|
|
17
|
+
"analyzeKeywordCannibalizationSinglePage",
|
|
18
|
+
"analyzeConversionElements",
|
|
19
|
+
"analyzeKeywordPlacement",
|
|
20
|
+
"analyzeTopicalClusters",
|
|
21
|
+
"analyzePlatformPresence",
|
|
22
|
+
"analyzeInteractiveTools",
|
|
23
|
+
"analyzeFunnelIntent",
|
|
24
|
+
"analyzeNavBoostSignals",
|
|
25
|
+
"analyzeEntitySEO",
|
|
26
|
+
"analyzeFreshnessSignals",
|
|
27
|
+
"analyzeAIContentStructure",
|
|
28
|
+
"analyzeRAGChunkReadiness",
|
|
29
|
+
"analyzeCitationQuality",
|
|
30
|
+
"analyzeAnswerConciseness",
|
|
31
|
+
"analyzeBrandMentionOptimization",
|
|
32
|
+
"analyzeAICitationWorthiness",
|
|
33
|
+
"analyzeReviewEcosystem"
|
|
34
|
+
];
|
|
35
|
+
var wasmModule = null;
|
|
36
|
+
var auditContext = null;
|
|
37
|
+
async function loadWasm() {
|
|
38
|
+
if (wasmModule) return wasmModule;
|
|
39
|
+
const possiblePaths = [
|
|
40
|
+
path.join(__dirname, "../native/pkg/rankcli_audit_engine.js"),
|
|
41
|
+
path.join(__dirname, "../wasm/rankcli_audit_engine.js"),
|
|
42
|
+
path.join(__dirname, "../../native/pkg/rankcli_audit_engine.js")
|
|
43
|
+
];
|
|
44
|
+
for (const wasmPath of possiblePaths) {
|
|
45
|
+
if (fs.existsSync(wasmPath)) {
|
|
46
|
+
wasmModule = __require(wasmPath);
|
|
47
|
+
return wasmModule;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
throw new Error(
|
|
51
|
+
`WASM engine not found. Run \`npm run build:wasm\` first.
|
|
52
|
+
Searched: ${possiblePaths.join(", ")}`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
async function initEngine(decryptPassphrase) {
|
|
56
|
+
const wasm = await loadWasm();
|
|
57
|
+
try {
|
|
58
|
+
wasm.init_engine(decryptPassphrase);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
throw new Error(`Failed to decrypt premium checks: ${error}`);
|
|
61
|
+
}
|
|
62
|
+
if (!wasm.is_initialized()) {
|
|
63
|
+
throw new Error("Failed to decrypt premium checks: engine did not initialize");
|
|
64
|
+
}
|
|
65
|
+
const jsCode = wasm.get_decrypted_code();
|
|
66
|
+
createExecutionContext(jsCode);
|
|
67
|
+
}
|
|
68
|
+
function createExecutionContext(jsCode) {
|
|
69
|
+
const sandbox = {
|
|
70
|
+
// Console for debugging (can be disabled in production)
|
|
71
|
+
console: {
|
|
72
|
+
log: () => {
|
|
73
|
+
},
|
|
74
|
+
warn: () => {
|
|
75
|
+
},
|
|
76
|
+
error: () => {
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
// Timers (some checks may use setTimeout)
|
|
80
|
+
setTimeout,
|
|
81
|
+
clearTimeout,
|
|
82
|
+
// JSON parsing
|
|
83
|
+
JSON,
|
|
84
|
+
// Math operations
|
|
85
|
+
Math,
|
|
86
|
+
// String/Array operations
|
|
87
|
+
String,
|
|
88
|
+
Array,
|
|
89
|
+
Object,
|
|
90
|
+
RegExp,
|
|
91
|
+
Date,
|
|
92
|
+
// URL parsing
|
|
93
|
+
URL,
|
|
94
|
+
URLSearchParams,
|
|
95
|
+
// Node.js globals needed by bundled dependencies (e.g., cheerio)
|
|
96
|
+
Buffer,
|
|
97
|
+
process: { env: {} },
|
|
98
|
+
// TextEncoder/TextDecoder for string handling
|
|
99
|
+
TextEncoder,
|
|
100
|
+
TextDecoder,
|
|
101
|
+
// The RankCLI namespace will be populated by the code
|
|
102
|
+
RankCLI: {},
|
|
103
|
+
// Global reference
|
|
104
|
+
globalThis: {}
|
|
105
|
+
};
|
|
106
|
+
sandbox.globalThis = sandbox;
|
|
107
|
+
auditContext = vm.createContext(sandbox);
|
|
108
|
+
try {
|
|
109
|
+
vm.runInContext(jsCode, auditContext, {
|
|
110
|
+
filename: "rankcli-checks.js",
|
|
111
|
+
timeout: 5e3
|
|
112
|
+
// 5 second timeout for initialization
|
|
113
|
+
});
|
|
114
|
+
} catch (error) {
|
|
115
|
+
throw new Error(`Failed to initialize audit checks: ${error}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function callSandboxCheck(fnName, html, url) {
|
|
119
|
+
if (!auditContext) {
|
|
120
|
+
throw new Error("Engine not initialized. Call initEngine() first.");
|
|
121
|
+
}
|
|
122
|
+
const script = new vm.Script(`
|
|
123
|
+
(function() {
|
|
124
|
+
if (typeof RankCLI.${fnName} !== 'function') {
|
|
125
|
+
throw new Error('RankCLI.${fnName} not found in decrypted code');
|
|
126
|
+
}
|
|
127
|
+
return JSON.stringify(RankCLI.${fnName}(${JSON.stringify(html)}, ${JSON.stringify(url)}));
|
|
128
|
+
})()
|
|
129
|
+
`);
|
|
130
|
+
const resultJson = script.runInContext(auditContext, { timeout: 1e4 });
|
|
131
|
+
return JSON.parse(resultJson);
|
|
132
|
+
}
|
|
133
|
+
async function getPremiumChecks() {
|
|
134
|
+
const wasm = await loadWasm();
|
|
135
|
+
if (!wasm.is_initialized()) {
|
|
136
|
+
throw new Error("Engine not initialized. Call initEngine() first.");
|
|
137
|
+
}
|
|
138
|
+
const checks = {};
|
|
139
|
+
for (const name of PREMIUM_CHECK_NAMES) {
|
|
140
|
+
checks[name] = (html, url) => callSandboxCheck(name, html, url);
|
|
141
|
+
}
|
|
142
|
+
return checks;
|
|
143
|
+
}
|
|
144
|
+
async function getVersion() {
|
|
145
|
+
const wasm = await loadWasm();
|
|
146
|
+
return wasm.version();
|
|
147
|
+
}
|
|
148
|
+
async function isReady() {
|
|
149
|
+
try {
|
|
150
|
+
const wasm = await loadWasm();
|
|
151
|
+
return wasm.is_initialized();
|
|
152
|
+
} catch {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export {
|
|
158
|
+
initEngine,
|
|
159
|
+
getPremiumChecks,
|
|
160
|
+
getVersion,
|
|
161
|
+
isReady
|
|
162
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -289,7 +289,7 @@ var import_chalk2 = __toESM(require("chalk"));
|
|
|
289
289
|
// src/lib/config.ts
|
|
290
290
|
var import_conf = __toESM(require("conf"));
|
|
291
291
|
var import_supabase_js = require("@supabase/supabase-js");
|
|
292
|
-
var INJECTED_CONFIG = {"supabaseUrl":"https://bspljbxwbjiqueeyzyat.supabase.co","supabaseAnonKey":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImJzcGxqYnh3YmppcXVlZXl6eWF0Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjkyNzQ1NTksImV4cCI6MjA4NDg1MDU1OX0.7gprxM2gT9cZ99sjZURLltrln78dEnwyFFKYpTzBaUU","webAppUrl":"https://rankcli.dev"};
|
|
292
|
+
var INJECTED_CONFIG = {"supabaseUrl":"https://bspljbxwbjiqueeyzyat.supabase.co","supabaseAnonKey":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImJzcGxqYnh3YmppcXVlZXl6eWF0Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjkyNzQ1NTksImV4cCI6MjA4NDg1MDU1OX0.7gprxM2gT9cZ99sjZURLltrln78dEnwyFFKYpTzBaUU","webAppUrl":"https://rankcli.dev","decryptPassphrase":"lKFLeIw/xGPa6E8c4XswZX/oiLtJf/yLF/Y6zwovte8="};
|
|
293
293
|
function getSupabaseUrl() {
|
|
294
294
|
const url = process.env.RANKCLI_SUPABASE_URL || process.env.VITE_SUPABASE_URL || INJECTED_CONFIG?.supabaseUrl;
|
|
295
295
|
if (!url) {
|
|
@@ -319,6 +319,9 @@ function getSupabaseAnonKeyLazy() {
|
|
|
319
319
|
return _supabaseAnonKey;
|
|
320
320
|
}
|
|
321
321
|
var WEB_APP_URL = process.env.RANKCLI_WEB_URL || INJECTED_CONFIG?.webAppUrl || "https://rankcli.dev";
|
|
322
|
+
function getDecryptPassphrase() {
|
|
323
|
+
return process.env.RANKCLI_BUILD_KEY || INJECTED_CONFIG?.decryptPassphrase;
|
|
324
|
+
}
|
|
322
325
|
function getApiUrl() {
|
|
323
326
|
return process.env.RANKCLI_API_URL || getSupabaseUrlLazy();
|
|
324
327
|
}
|
|
@@ -462,6 +465,7 @@ function getUserInfo() {
|
|
|
462
465
|
return {
|
|
463
466
|
userId: config.get("userId"),
|
|
464
467
|
email: config.get("email"),
|
|
468
|
+
planId: config.get("planId"),
|
|
465
469
|
planName: config.get("planName") || "Free",
|
|
466
470
|
sitesLimit: config.get("sitesLimit") || 1
|
|
467
471
|
};
|
|
@@ -664,6 +668,143 @@ function hasFeature(tier, feature) {
|
|
|
664
668
|
return limits?.features?.[feature] ?? false;
|
|
665
669
|
}
|
|
666
670
|
|
|
671
|
+
// src/wasm-engine.ts
|
|
672
|
+
var vm = __toESM(require("vm"));
|
|
673
|
+
var path = __toESM(require("path"));
|
|
674
|
+
var fs = __toESM(require("fs"));
|
|
675
|
+
var PREMIUM_CHECK_NAMES = [
|
|
676
|
+
"analyzeFeaturedSnippet",
|
|
677
|
+
"analyzeInternalLinkGraph",
|
|
678
|
+
"analyzeEEATSignals",
|
|
679
|
+
"analyzeContentScience",
|
|
680
|
+
"analyzeKeywordCannibalizationSinglePage",
|
|
681
|
+
"analyzeConversionElements",
|
|
682
|
+
"analyzeKeywordPlacement",
|
|
683
|
+
"analyzeTopicalClusters",
|
|
684
|
+
"analyzePlatformPresence",
|
|
685
|
+
"analyzeInteractiveTools",
|
|
686
|
+
"analyzeFunnelIntent",
|
|
687
|
+
"analyzeNavBoostSignals",
|
|
688
|
+
"analyzeEntitySEO",
|
|
689
|
+
"analyzeFreshnessSignals",
|
|
690
|
+
"analyzeAIContentStructure",
|
|
691
|
+
"analyzeRAGChunkReadiness",
|
|
692
|
+
"analyzeCitationQuality",
|
|
693
|
+
"analyzeAnswerConciseness",
|
|
694
|
+
"analyzeBrandMentionOptimization",
|
|
695
|
+
"analyzeAICitationWorthiness",
|
|
696
|
+
"analyzeReviewEcosystem"
|
|
697
|
+
];
|
|
698
|
+
var wasmModule = null;
|
|
699
|
+
var auditContext = null;
|
|
700
|
+
async function loadWasm() {
|
|
701
|
+
if (wasmModule) return wasmModule;
|
|
702
|
+
const possiblePaths = [
|
|
703
|
+
path.join(__dirname, "../native/pkg/rankcli_audit_engine.js"),
|
|
704
|
+
path.join(__dirname, "../wasm/rankcli_audit_engine.js"),
|
|
705
|
+
path.join(__dirname, "../../native/pkg/rankcli_audit_engine.js")
|
|
706
|
+
];
|
|
707
|
+
for (const wasmPath of possiblePaths) {
|
|
708
|
+
if (fs.existsSync(wasmPath)) {
|
|
709
|
+
wasmModule = require(wasmPath);
|
|
710
|
+
return wasmModule;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
throw new Error(
|
|
714
|
+
`WASM engine not found. Run \`npm run build:wasm\` first.
|
|
715
|
+
Searched: ${possiblePaths.join(", ")}`
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
async function initEngine(decryptPassphrase) {
|
|
719
|
+
const wasm = await loadWasm();
|
|
720
|
+
try {
|
|
721
|
+
wasm.init_engine(decryptPassphrase);
|
|
722
|
+
} catch (error) {
|
|
723
|
+
throw new Error(`Failed to decrypt premium checks: ${error}`);
|
|
724
|
+
}
|
|
725
|
+
if (!wasm.is_initialized()) {
|
|
726
|
+
throw new Error("Failed to decrypt premium checks: engine did not initialize");
|
|
727
|
+
}
|
|
728
|
+
const jsCode = wasm.get_decrypted_code();
|
|
729
|
+
createExecutionContext(jsCode);
|
|
730
|
+
}
|
|
731
|
+
function createExecutionContext(jsCode) {
|
|
732
|
+
const sandbox = {
|
|
733
|
+
// Console for debugging (can be disabled in production)
|
|
734
|
+
console: {
|
|
735
|
+
log: () => {
|
|
736
|
+
},
|
|
737
|
+
warn: () => {
|
|
738
|
+
},
|
|
739
|
+
error: () => {
|
|
740
|
+
}
|
|
741
|
+
},
|
|
742
|
+
// Timers (some checks may use setTimeout)
|
|
743
|
+
setTimeout,
|
|
744
|
+
clearTimeout,
|
|
745
|
+
// JSON parsing
|
|
746
|
+
JSON,
|
|
747
|
+
// Math operations
|
|
748
|
+
Math,
|
|
749
|
+
// String/Array operations
|
|
750
|
+
String,
|
|
751
|
+
Array,
|
|
752
|
+
Object,
|
|
753
|
+
RegExp,
|
|
754
|
+
Date,
|
|
755
|
+
// URL parsing
|
|
756
|
+
URL,
|
|
757
|
+
URLSearchParams,
|
|
758
|
+
// Node.js globals needed by bundled dependencies (e.g., cheerio)
|
|
759
|
+
Buffer,
|
|
760
|
+
process: { env: {} },
|
|
761
|
+
// TextEncoder/TextDecoder for string handling
|
|
762
|
+
TextEncoder,
|
|
763
|
+
TextDecoder,
|
|
764
|
+
// The RankCLI namespace will be populated by the code
|
|
765
|
+
RankCLI: {},
|
|
766
|
+
// Global reference
|
|
767
|
+
globalThis: {}
|
|
768
|
+
};
|
|
769
|
+
sandbox.globalThis = sandbox;
|
|
770
|
+
auditContext = vm.createContext(sandbox);
|
|
771
|
+
try {
|
|
772
|
+
vm.runInContext(jsCode, auditContext, {
|
|
773
|
+
filename: "rankcli-checks.js",
|
|
774
|
+
timeout: 5e3
|
|
775
|
+
// 5 second timeout for initialization
|
|
776
|
+
});
|
|
777
|
+
} catch (error) {
|
|
778
|
+
throw new Error(`Failed to initialize audit checks: ${error}`);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
function callSandboxCheck(fnName, html, url) {
|
|
782
|
+
if (!auditContext) {
|
|
783
|
+
throw new Error("Engine not initialized. Call initEngine() first.");
|
|
784
|
+
}
|
|
785
|
+
const script = new vm.Script(`
|
|
786
|
+
(function() {
|
|
787
|
+
if (typeof RankCLI.${fnName} !== 'function') {
|
|
788
|
+
throw new Error('RankCLI.${fnName} not found in decrypted code');
|
|
789
|
+
}
|
|
790
|
+
return JSON.stringify(RankCLI.${fnName}(${JSON.stringify(html)}, ${JSON.stringify(url)}));
|
|
791
|
+
})()
|
|
792
|
+
`);
|
|
793
|
+
const resultJson = script.runInContext(auditContext, { timeout: 1e4 });
|
|
794
|
+
return JSON.parse(resultJson);
|
|
795
|
+
}
|
|
796
|
+
async function getPremiumChecks() {
|
|
797
|
+
const wasm = await loadWasm();
|
|
798
|
+
if (!wasm.is_initialized()) {
|
|
799
|
+
throw new Error("Engine not initialized. Call initEngine() first.");
|
|
800
|
+
}
|
|
801
|
+
const checks = {};
|
|
802
|
+
for (const name of PREMIUM_CHECK_NAMES) {
|
|
803
|
+
checks[name] = (html, url) => callSandboxCheck(name, html, url);
|
|
804
|
+
}
|
|
805
|
+
return checks;
|
|
806
|
+
}
|
|
807
|
+
|
|
667
808
|
// src/commands/audit.ts
|
|
668
809
|
var { detectFramework: detectFramework2, getFrameworkDisplayName, getFrameworkIssues } = import_agent_runtime.frameworks;
|
|
669
810
|
async function fetchHtml(url, timeout = 1e4) {
|
|
@@ -868,6 +1009,21 @@ async function audit(options) {
|
|
|
868
1009
|
const aiProvider = options.aiProvider || "openai";
|
|
869
1010
|
const aiKey = options.aiKey || (aiProvider === "anthropic" ? process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY : process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY) || "";
|
|
870
1011
|
const loggedIn = isLoggedIn();
|
|
1012
|
+
const { planId } = getUserInfo();
|
|
1013
|
+
const tierId = planId && planId in TIER_LIMITS ? planId : loggedIn ? "free" : "free_anon";
|
|
1014
|
+
const checksLimit = TIER_LIMITS[tierId].checks;
|
|
1015
|
+
let premiumChecks;
|
|
1016
|
+
if (checksLimit > TIER_LIMITS.free.checks) {
|
|
1017
|
+
const passphrase = getDecryptPassphrase();
|
|
1018
|
+
if (passphrase) {
|
|
1019
|
+
try {
|
|
1020
|
+
await initEngine(passphrase);
|
|
1021
|
+
premiumChecks = await getPremiumChecks();
|
|
1022
|
+
} catch (error) {
|
|
1023
|
+
console.error(import_chalk2.default.dim(`(premium checks unavailable: ${error instanceof Error ? error.message : error})`));
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
871
1027
|
if (!url) {
|
|
872
1028
|
console.log("\n\u26A0\uFE0F Please provide a URL to audit:");
|
|
873
1029
|
console.log(" rankcli audit --url https://your-site.com");
|
|
@@ -912,7 +1068,9 @@ async function audit(options) {
|
|
|
912
1068
|
const report = await (0, import_agent_runtime.runFullAudit)({
|
|
913
1069
|
url: pageUrl,
|
|
914
1070
|
maxPages: 1,
|
|
915
|
-
checkBrokenLinks: options.checkLinks || false
|
|
1071
|
+
checkBrokenLinks: options.checkLinks || false,
|
|
1072
|
+
checksLimit,
|
|
1073
|
+
premiumChecks
|
|
916
1074
|
});
|
|
917
1075
|
auditSpinner.text = `Auditing pages... (${index + 1}/${pagesToAudit.length} complete)`;
|
|
918
1076
|
return { report, url: pageUrl };
|
|
@@ -2673,13 +2831,13 @@ function extractBodyText(html) {
|
|
|
2673
2831
|
|
|
2674
2832
|
// src/commands/setup.ts
|
|
2675
2833
|
var readline2 = __toESM(require("readline"));
|
|
2676
|
-
var
|
|
2677
|
-
var
|
|
2834
|
+
var fs2 = __toESM(require("fs"));
|
|
2835
|
+
var path2 = __toESM(require("path"));
|
|
2678
2836
|
var import_agent_runtime8 = require("@rankcli/agent-runtime");
|
|
2679
2837
|
async function setup(options) {
|
|
2680
2838
|
console.log("\n\u{1F527} SEO Autopilot - Setup\n");
|
|
2681
2839
|
const projectPath = process.cwd();
|
|
2682
|
-
if (!
|
|
2840
|
+
if (!fs2.existsSync(path2.join(projectPath, "package.json")) && !fs2.existsSync(path2.join(projectPath, "index.html"))) {
|
|
2683
2841
|
console.log("\u26A0\uFE0F No package.json or index.html found. Are you in a web project directory?\n");
|
|
2684
2842
|
}
|
|
2685
2843
|
if (options.all || !options.ga4 && !options.gsc && !options.githubAction) {
|
|
@@ -2790,16 +2948,16 @@ async function setupGA4(projectPath, measurementId) {
|
|
|
2790
2948
|
console.log(result.code);
|
|
2791
2949
|
}
|
|
2792
2950
|
}
|
|
2793
|
-
const envExamplePath =
|
|
2951
|
+
const envExamplePath = path2.join(projectPath, ".env.example");
|
|
2794
2952
|
const envContent = (0, import_agent_runtime8.generateGA4EnvTemplate)();
|
|
2795
|
-
if (
|
|
2796
|
-
const existing =
|
|
2953
|
+
if (fs2.existsSync(envExamplePath)) {
|
|
2954
|
+
const existing = fs2.readFileSync(envExamplePath, "utf-8");
|
|
2797
2955
|
if (!existing.includes("GA4_MEASUREMENT_ID")) {
|
|
2798
|
-
|
|
2956
|
+
fs2.appendFileSync(envExamplePath, "\n" + envContent);
|
|
2799
2957
|
console.log(" Updated: .env.example");
|
|
2800
2958
|
}
|
|
2801
2959
|
} else {
|
|
2802
|
-
|
|
2960
|
+
fs2.writeFileSync(envExamplePath, envContent);
|
|
2803
2961
|
console.log(" Created: .env.example");
|
|
2804
2962
|
}
|
|
2805
2963
|
console.log("");
|
|
@@ -2833,7 +2991,7 @@ async function setupGitHubAction(projectPath, schedule, siteUrl) {
|
|
|
2833
2991
|
const { files, instructions } = (0, import_agent_runtime8.writeGitHubActionFiles)(projectPath, config2);
|
|
2834
2992
|
console.log("\u2705 GitHub Action files created:");
|
|
2835
2993
|
for (const file of files) {
|
|
2836
|
-
console.log(` - ${
|
|
2994
|
+
console.log(` - ${path2.relative(projectPath, file)}`);
|
|
2837
2995
|
}
|
|
2838
2996
|
console.log(instructions);
|
|
2839
2997
|
}
|
|
@@ -3224,7 +3382,7 @@ async function fetchAndSaveSubscription() {
|
|
|
3224
3382
|
}
|
|
3225
3383
|
|
|
3226
3384
|
// src/version.ts
|
|
3227
|
-
var VERSION = "0.0.
|
|
3385
|
+
var VERSION = "0.0.22";
|
|
3228
3386
|
|
|
3229
3387
|
// src/index.ts
|
|
3230
3388
|
var program = new import_commander.Command();
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
2
|
+
import {
|
|
3
|
+
getPremiumChecks,
|
|
4
|
+
initEngine
|
|
5
|
+
} from "./chunk-YX67Q5F6.mjs";
|
|
3
6
|
|
|
4
7
|
// src/index.ts
|
|
5
8
|
import { Command } from "commander";
|
|
@@ -267,7 +270,7 @@ import chalk2 from "chalk";
|
|
|
267
270
|
// src/lib/config.ts
|
|
268
271
|
import Conf from "conf";
|
|
269
272
|
import { createClient } from "@supabase/supabase-js";
|
|
270
|
-
const INJECTED_CONFIG = {"supabaseUrl":"https://bspljbxwbjiqueeyzyat.supabase.co","supabaseAnonKey":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImJzcGxqYnh3YmppcXVlZXl6eWF0Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjkyNzQ1NTksImV4cCI6MjA4NDg1MDU1OX0.7gprxM2gT9cZ99sjZURLltrln78dEnwyFFKYpTzBaUU","webAppUrl":"https://rankcli.dev"};
|
|
273
|
+
const INJECTED_CONFIG = {"supabaseUrl":"https://bspljbxwbjiqueeyzyat.supabase.co","supabaseAnonKey":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImJzcGxqYnh3YmppcXVlZXl6eWF0Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjkyNzQ1NTksImV4cCI6MjA4NDg1MDU1OX0.7gprxM2gT9cZ99sjZURLltrln78dEnwyFFKYpTzBaUU","webAppUrl":"https://rankcli.dev","decryptPassphrase":"lKFLeIw/xGPa6E8c4XswZX/oiLtJf/yLF/Y6zwovte8="};
|
|
271
274
|
function getSupabaseUrl() {
|
|
272
275
|
const url = process.env.RANKCLI_SUPABASE_URL || process.env.VITE_SUPABASE_URL || INJECTED_CONFIG?.supabaseUrl;
|
|
273
276
|
if (!url) {
|
|
@@ -297,6 +300,9 @@ function getSupabaseAnonKeyLazy() {
|
|
|
297
300
|
return _supabaseAnonKey;
|
|
298
301
|
}
|
|
299
302
|
var WEB_APP_URL = process.env.RANKCLI_WEB_URL || INJECTED_CONFIG?.webAppUrl || "https://rankcli.dev";
|
|
303
|
+
function getDecryptPassphrase() {
|
|
304
|
+
return process.env.RANKCLI_BUILD_KEY || INJECTED_CONFIG?.decryptPassphrase;
|
|
305
|
+
}
|
|
300
306
|
function getApiUrl() {
|
|
301
307
|
return process.env.RANKCLI_API_URL || getSupabaseUrlLazy();
|
|
302
308
|
}
|
|
@@ -440,6 +446,7 @@ function getUserInfo() {
|
|
|
440
446
|
return {
|
|
441
447
|
userId: config.get("userId"),
|
|
442
448
|
email: config.get("email"),
|
|
449
|
+
planId: config.get("planId"),
|
|
443
450
|
planName: config.get("planName") || "Free",
|
|
444
451
|
sitesLimit: config.get("sitesLimit") || 1
|
|
445
452
|
};
|
|
@@ -846,6 +853,21 @@ async function audit(options) {
|
|
|
846
853
|
const aiProvider = options.aiProvider || "openai";
|
|
847
854
|
const aiKey = options.aiKey || (aiProvider === "anthropic" ? process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY : process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY) || "";
|
|
848
855
|
const loggedIn = isLoggedIn();
|
|
856
|
+
const { planId } = getUserInfo();
|
|
857
|
+
const tierId = planId && planId in TIER_LIMITS ? planId : loggedIn ? "free" : "free_anon";
|
|
858
|
+
const checksLimit = TIER_LIMITS[tierId].checks;
|
|
859
|
+
let premiumChecks;
|
|
860
|
+
if (checksLimit > TIER_LIMITS.free.checks) {
|
|
861
|
+
const passphrase = getDecryptPassphrase();
|
|
862
|
+
if (passphrase) {
|
|
863
|
+
try {
|
|
864
|
+
await initEngine(passphrase);
|
|
865
|
+
premiumChecks = await getPremiumChecks();
|
|
866
|
+
} catch (error) {
|
|
867
|
+
console.error(chalk2.dim(`(premium checks unavailable: ${error instanceof Error ? error.message : error})`));
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
849
871
|
if (!url) {
|
|
850
872
|
console.log("\n\u26A0\uFE0F Please provide a URL to audit:");
|
|
851
873
|
console.log(" rankcli audit --url https://your-site.com");
|
|
@@ -890,7 +912,9 @@ async function audit(options) {
|
|
|
890
912
|
const report = await runFullAudit({
|
|
891
913
|
url: pageUrl,
|
|
892
914
|
maxPages: 1,
|
|
893
|
-
checkBrokenLinks: options.checkLinks || false
|
|
915
|
+
checkBrokenLinks: options.checkLinks || false,
|
|
916
|
+
checksLimit,
|
|
917
|
+
premiumChecks
|
|
894
918
|
});
|
|
895
919
|
auditSpinner.text = `Auditing pages... (${index + 1}/${pagesToAudit.length} complete)`;
|
|
896
920
|
return { report, url: pageUrl };
|
|
@@ -1615,7 +1639,7 @@ ${chalk3.green("\u2705")} Changes committed`);
|
|
|
1615
1639
|
import ora3 from "ora";
|
|
1616
1640
|
import chalk4 from "chalk";
|
|
1617
1641
|
import { table } from "table";
|
|
1618
|
-
import { RankTracker, TIER_LIMITS as
|
|
1642
|
+
import { RankTracker, TIER_LIMITS as TIER_LIMITS2 } from "@rankcli/agent-runtime";
|
|
1619
1643
|
async function getProjectForUrl(url) {
|
|
1620
1644
|
const supabase = await getAuthenticatedClient();
|
|
1621
1645
|
if (!supabase) return null;
|
|
@@ -1645,7 +1669,7 @@ async function getProjectForUrl(url) {
|
|
|
1645
1669
|
};
|
|
1646
1670
|
}
|
|
1647
1671
|
function getTierLimits(tier) {
|
|
1648
|
-
const limits =
|
|
1672
|
+
const limits = TIER_LIMITS2[tier] || TIER_LIMITS2.free;
|
|
1649
1673
|
return {
|
|
1650
1674
|
maxKeywords: limits.maxKeywords,
|
|
1651
1675
|
checksPerDay: limits.checksPerDay
|
|
@@ -3241,7 +3265,7 @@ async function fetchAndSaveSubscription() {
|
|
|
3241
3265
|
}
|
|
3242
3266
|
|
|
3243
3267
|
// src/version.ts
|
|
3244
|
-
var VERSION = "0.0.
|
|
3268
|
+
var VERSION = "0.0.22";
|
|
3245
3269
|
|
|
3246
3270
|
// src/index.ts
|
|
3247
3271
|
var program = new Command();
|
package/dist/wasm-engine.d.mts
CHANGED
|
@@ -1,55 +1,43 @@
|
|
|
1
|
+
import { AuditIssue } from '@rankcli/agent-runtime';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* WASM Engine Wrapper
|
|
3
5
|
*
|
|
4
|
-
*
|
|
5
|
-
* with Node.js execution
|
|
6
|
+
* Bridges the WASM binary (which contains AES-encrypted paid-tier check
|
|
7
|
+
* source) with Node.js execution:
|
|
6
8
|
*
|
|
7
|
-
* 1. WASM decrypts the JS code using
|
|
9
|
+
* 1. WASM decrypts the JS code using a fixed, build-embedded passphrase
|
|
8
10
|
* 2. We get the decrypted code from WASM
|
|
9
11
|
* 3. We execute it in a Node.js VM sandbox
|
|
10
|
-
* 4.
|
|
12
|
+
* 4. Individual premium check functions are called from that sandbox
|
|
11
13
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
+
* This is code protection, not access control: entitlement (is this user
|
|
15
|
+
* actually paid?) is decided by the caller, from the real Supabase-backed
|
|
16
|
+
* apiKey/plan the CLI validates on login - see commands/audit.ts. This
|
|
17
|
+
* module should only ever be initialized after that check passes; there
|
|
18
|
+
* is no per-user license key, and passing an arbitrary string here does
|
|
19
|
+
* not grant anything a validated apiKey didn't already grant.
|
|
14
20
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
score: number;
|
|
21
|
+
|
|
22
|
+
type PremiumCheckFn = (html: string, url: string) => {
|
|
18
23
|
issues: AuditIssue[];
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
code: string;
|
|
24
|
-
severity: 'error' | 'warning' | 'notice';
|
|
25
|
-
category: string;
|
|
26
|
-
title: string;
|
|
27
|
-
description?: string;
|
|
28
|
-
impact?: string;
|
|
29
|
-
howToFix?: string;
|
|
30
|
-
affectedUrls?: string[];
|
|
31
|
-
}
|
|
32
|
-
type Tier = 'free' | 'registered' | 'solo' | 'pro' | 'agency';
|
|
33
|
-
/**
|
|
34
|
-
* Initialize the engine with a license key
|
|
35
|
-
*/
|
|
36
|
-
declare function initEngine(licenseKey?: string): Promise<void>;
|
|
37
|
-
/**
|
|
38
|
-
* Run an SEO audit on a URL
|
|
39
|
-
*/
|
|
40
|
-
declare function runAudit(url: string, html: string): Promise<AuditResult>;
|
|
41
|
-
/**
|
|
42
|
-
* Get the current tier
|
|
43
|
-
*/
|
|
44
|
-
declare function getTier(): Promise<Tier>;
|
|
24
|
+
data: unknown;
|
|
25
|
+
};
|
|
26
|
+
declare const PREMIUM_CHECK_NAMES: readonly ["analyzeFeaturedSnippet", "analyzeInternalLinkGraph", "analyzeEEATSignals", "analyzeContentScience", "analyzeKeywordCannibalizationSinglePage", "analyzeConversionElements", "analyzeKeywordPlacement", "analyzeTopicalClusters", "analyzePlatformPresence", "analyzeInteractiveTools", "analyzeFunnelIntent", "analyzeNavBoostSignals", "analyzeEntitySEO", "analyzeFreshnessSignals", "analyzeAIContentStructure", "analyzeRAGChunkReadiness", "analyzeCitationQuality", "analyzeAnswerConciseness", "analyzeBrandMentionOptimization", "analyzeAICitationWorthiness", "analyzeReviewEcosystem"];
|
|
27
|
+
type PremiumChecks = Record<(typeof PREMIUM_CHECK_NAMES)[number], PremiumCheckFn>;
|
|
45
28
|
/**
|
|
46
|
-
*
|
|
29
|
+
* Decrypt the embedded premium checks and prepare them for execution.
|
|
30
|
+
* Only call this once the caller has independently confirmed the user is
|
|
31
|
+
* entitled to premium checks (a validated paid apiKey) - see the module
|
|
32
|
+
* doc comment above.
|
|
47
33
|
*/
|
|
48
|
-
declare function
|
|
34
|
+
declare function initEngine(decryptPassphrase: string): Promise<void>;
|
|
49
35
|
/**
|
|
50
|
-
*
|
|
36
|
+
* Build a PremiumChecks object (matching agent-runtime's
|
|
37
|
+
* premium-checks.types.ts) whose methods each call into the decrypted
|
|
38
|
+
* VM sandbox. Pass the result as runFullAudit({ premiumChecks }).
|
|
51
39
|
*/
|
|
52
|
-
declare function
|
|
40
|
+
declare function getPremiumChecks(): Promise<PremiumChecks>;
|
|
53
41
|
/**
|
|
54
42
|
* Get engine version info
|
|
55
43
|
*/
|
|
@@ -59,4 +47,4 @@ declare function getVersion(): Promise<string>;
|
|
|
59
47
|
*/
|
|
60
48
|
declare function isReady(): Promise<boolean>;
|
|
61
49
|
|
|
62
|
-
export { type
|
|
50
|
+
export { type PremiumCheckFn, type PremiumChecks, getPremiumChecks, getVersion, initEngine, isReady };
|
package/dist/wasm-engine.d.ts
CHANGED
|
@@ -1,55 +1,43 @@
|
|
|
1
|
+
import { AuditIssue } from '@rankcli/agent-runtime';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* WASM Engine Wrapper
|
|
3
5
|
*
|
|
4
|
-
*
|
|
5
|
-
* with Node.js execution
|
|
6
|
+
* Bridges the WASM binary (which contains AES-encrypted paid-tier check
|
|
7
|
+
* source) with Node.js execution:
|
|
6
8
|
*
|
|
7
|
-
* 1. WASM decrypts the JS code using
|
|
9
|
+
* 1. WASM decrypts the JS code using a fixed, build-embedded passphrase
|
|
8
10
|
* 2. We get the decrypted code from WASM
|
|
9
11
|
* 3. We execute it in a Node.js VM sandbox
|
|
10
|
-
* 4.
|
|
12
|
+
* 4. Individual premium check functions are called from that sandbox
|
|
11
13
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
+
* This is code protection, not access control: entitlement (is this user
|
|
15
|
+
* actually paid?) is decided by the caller, from the real Supabase-backed
|
|
16
|
+
* apiKey/plan the CLI validates on login - see commands/audit.ts. This
|
|
17
|
+
* module should only ever be initialized after that check passes; there
|
|
18
|
+
* is no per-user license key, and passing an arbitrary string here does
|
|
19
|
+
* not grant anything a validated apiKey didn't already grant.
|
|
14
20
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
score: number;
|
|
21
|
+
|
|
22
|
+
type PremiumCheckFn = (html: string, url: string) => {
|
|
18
23
|
issues: AuditIssue[];
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
code: string;
|
|
24
|
-
severity: 'error' | 'warning' | 'notice';
|
|
25
|
-
category: string;
|
|
26
|
-
title: string;
|
|
27
|
-
description?: string;
|
|
28
|
-
impact?: string;
|
|
29
|
-
howToFix?: string;
|
|
30
|
-
affectedUrls?: string[];
|
|
31
|
-
}
|
|
32
|
-
type Tier = 'free' | 'registered' | 'solo' | 'pro' | 'agency';
|
|
33
|
-
/**
|
|
34
|
-
* Initialize the engine with a license key
|
|
35
|
-
*/
|
|
36
|
-
declare function initEngine(licenseKey?: string): Promise<void>;
|
|
37
|
-
/**
|
|
38
|
-
* Run an SEO audit on a URL
|
|
39
|
-
*/
|
|
40
|
-
declare function runAudit(url: string, html: string): Promise<AuditResult>;
|
|
41
|
-
/**
|
|
42
|
-
* Get the current tier
|
|
43
|
-
*/
|
|
44
|
-
declare function getTier(): Promise<Tier>;
|
|
24
|
+
data: unknown;
|
|
25
|
+
};
|
|
26
|
+
declare const PREMIUM_CHECK_NAMES: readonly ["analyzeFeaturedSnippet", "analyzeInternalLinkGraph", "analyzeEEATSignals", "analyzeContentScience", "analyzeKeywordCannibalizationSinglePage", "analyzeConversionElements", "analyzeKeywordPlacement", "analyzeTopicalClusters", "analyzePlatformPresence", "analyzeInteractiveTools", "analyzeFunnelIntent", "analyzeNavBoostSignals", "analyzeEntitySEO", "analyzeFreshnessSignals", "analyzeAIContentStructure", "analyzeRAGChunkReadiness", "analyzeCitationQuality", "analyzeAnswerConciseness", "analyzeBrandMentionOptimization", "analyzeAICitationWorthiness", "analyzeReviewEcosystem"];
|
|
27
|
+
type PremiumChecks = Record<(typeof PREMIUM_CHECK_NAMES)[number], PremiumCheckFn>;
|
|
45
28
|
/**
|
|
46
|
-
*
|
|
29
|
+
* Decrypt the embedded premium checks and prepare them for execution.
|
|
30
|
+
* Only call this once the caller has independently confirmed the user is
|
|
31
|
+
* entitled to premium checks (a validated paid apiKey) - see the module
|
|
32
|
+
* doc comment above.
|
|
47
33
|
*/
|
|
48
|
-
declare function
|
|
34
|
+
declare function initEngine(decryptPassphrase: string): Promise<void>;
|
|
49
35
|
/**
|
|
50
|
-
*
|
|
36
|
+
* Build a PremiumChecks object (matching agent-runtime's
|
|
37
|
+
* premium-checks.types.ts) whose methods each call into the decrypted
|
|
38
|
+
* VM sandbox. Pass the result as runFullAudit({ premiumChecks }).
|
|
51
39
|
*/
|
|
52
|
-
declare function
|
|
40
|
+
declare function getPremiumChecks(): Promise<PremiumChecks>;
|
|
53
41
|
/**
|
|
54
42
|
* Get engine version info
|
|
55
43
|
*/
|
|
@@ -59,4 +47,4 @@ declare function getVersion(): Promise<string>;
|
|
|
59
47
|
*/
|
|
60
48
|
declare function isReady(): Promise<boolean>;
|
|
61
49
|
|
|
62
|
-
export { type
|
|
50
|
+
export { type PremiumCheckFn, type PremiumChecks, getPremiumChecks, getVersion, initEngine, isReady };
|
package/dist/wasm-engine.js
CHANGED
|
@@ -30,18 +30,38 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/wasm-engine.ts
|
|
31
31
|
var wasm_engine_exports = {};
|
|
32
32
|
__export(wasm_engine_exports, {
|
|
33
|
-
|
|
34
|
-
getTier: () => getTier,
|
|
33
|
+
getPremiumChecks: () => getPremiumChecks,
|
|
35
34
|
getVersion: () => getVersion,
|
|
36
35
|
initEngine: () => initEngine,
|
|
37
|
-
isReady: () => isReady
|
|
38
|
-
runAudit: () => runAudit,
|
|
39
|
-
validateLicense: () => validateLicense
|
|
36
|
+
isReady: () => isReady
|
|
40
37
|
});
|
|
41
38
|
module.exports = __toCommonJS(wasm_engine_exports);
|
|
42
39
|
var vm = __toESM(require("vm"));
|
|
43
40
|
var path = __toESM(require("path"));
|
|
44
41
|
var fs = __toESM(require("fs"));
|
|
42
|
+
var PREMIUM_CHECK_NAMES = [
|
|
43
|
+
"analyzeFeaturedSnippet",
|
|
44
|
+
"analyzeInternalLinkGraph",
|
|
45
|
+
"analyzeEEATSignals",
|
|
46
|
+
"analyzeContentScience",
|
|
47
|
+
"analyzeKeywordCannibalizationSinglePage",
|
|
48
|
+
"analyzeConversionElements",
|
|
49
|
+
"analyzeKeywordPlacement",
|
|
50
|
+
"analyzeTopicalClusters",
|
|
51
|
+
"analyzePlatformPresence",
|
|
52
|
+
"analyzeInteractiveTools",
|
|
53
|
+
"analyzeFunnelIntent",
|
|
54
|
+
"analyzeNavBoostSignals",
|
|
55
|
+
"analyzeEntitySEO",
|
|
56
|
+
"analyzeFreshnessSignals",
|
|
57
|
+
"analyzeAIContentStructure",
|
|
58
|
+
"analyzeRAGChunkReadiness",
|
|
59
|
+
"analyzeCitationQuality",
|
|
60
|
+
"analyzeAnswerConciseness",
|
|
61
|
+
"analyzeBrandMentionOptimization",
|
|
62
|
+
"analyzeAICitationWorthiness",
|
|
63
|
+
"analyzeReviewEcosystem"
|
|
64
|
+
];
|
|
45
65
|
var wasmModule = null;
|
|
46
66
|
var auditContext = null;
|
|
47
67
|
async function loadWasm() {
|
|
@@ -62,16 +82,15 @@ async function loadWasm() {
|
|
|
62
82
|
Searched: ${possiblePaths.join(", ")}`
|
|
63
83
|
);
|
|
64
84
|
}
|
|
65
|
-
async function initEngine(
|
|
85
|
+
async function initEngine(decryptPassphrase) {
|
|
66
86
|
const wasm = await loadWasm();
|
|
67
|
-
const key = licenseKey || process.env.RANKCLI_LICENSE_KEY || "";
|
|
68
87
|
try {
|
|
69
|
-
wasm.init_engine(
|
|
88
|
+
wasm.init_engine(decryptPassphrase);
|
|
70
89
|
} catch (error) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
throw
|
|
90
|
+
throw new Error(`Failed to decrypt premium checks: ${error}`);
|
|
91
|
+
}
|
|
92
|
+
if (!wasm.is_initialized()) {
|
|
93
|
+
throw new Error("Failed to decrypt premium checks: engine did not initialize");
|
|
75
94
|
}
|
|
76
95
|
const jsCode = wasm.get_decrypted_code();
|
|
77
96
|
createExecutionContext(jsCode);
|
|
@@ -126,59 +145,31 @@ function createExecutionContext(jsCode) {
|
|
|
126
145
|
throw new Error(`Failed to initialize audit checks: ${error}`);
|
|
127
146
|
}
|
|
128
147
|
}
|
|
129
|
-
|
|
148
|
+
function callSandboxCheck(fnName, html, url) {
|
|
149
|
+
if (!auditContext) {
|
|
150
|
+
throw new Error("Engine not initialized. Call initEngine() first.");
|
|
151
|
+
}
|
|
152
|
+
const script = new vm.Script(`
|
|
153
|
+
(function() {
|
|
154
|
+
if (typeof RankCLI.${fnName} !== 'function') {
|
|
155
|
+
throw new Error('RankCLI.${fnName} not found in decrypted code');
|
|
156
|
+
}
|
|
157
|
+
return JSON.stringify(RankCLI.${fnName}(${JSON.stringify(html)}, ${JSON.stringify(url)}));
|
|
158
|
+
})()
|
|
159
|
+
`);
|
|
160
|
+
const resultJson = script.runInContext(auditContext, { timeout: 1e4 });
|
|
161
|
+
return JSON.parse(resultJson);
|
|
162
|
+
}
|
|
163
|
+
async function getPremiumChecks() {
|
|
130
164
|
const wasm = await loadWasm();
|
|
131
165
|
if (!wasm.is_initialized()) {
|
|
132
166
|
throw new Error("Engine not initialized. Call initEngine() first.");
|
|
133
167
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const checksLimit = wasm.get_checks_limit();
|
|
138
|
-
const tier = wasm.get_tier();
|
|
139
|
-
try {
|
|
140
|
-
const script = new vm.Script(`
|
|
141
|
-
(function() {
|
|
142
|
-
const url = ${JSON.stringify(url)};
|
|
143
|
-
const html = ${JSON.stringify(html)};
|
|
144
|
-
const checksLimit = ${checksLimit};
|
|
145
|
-
|
|
146
|
-
if (typeof RankCLI.runAudit === 'function') {
|
|
147
|
-
return JSON.stringify(RankCLI.runAudit(url, html, checksLimit));
|
|
148
|
-
} else if (typeof RankCLI.runFullAudit === 'function') {
|
|
149
|
-
// Synchronous wrapper for async function
|
|
150
|
-
return JSON.stringify({
|
|
151
|
-
success: true,
|
|
152
|
-
score: 85,
|
|
153
|
-
issues: [],
|
|
154
|
-
checksRun: checksLimit,
|
|
155
|
-
tier: "${tier}"
|
|
156
|
-
});
|
|
157
|
-
} else {
|
|
158
|
-
throw new Error('RankCLI.runAudit not found in decrypted code');
|
|
159
|
-
}
|
|
160
|
-
})()
|
|
161
|
-
`);
|
|
162
|
-
const resultJson = script.runInContext(auditContext, {
|
|
163
|
-
timeout: 3e4
|
|
164
|
-
// 30 second timeout for audit
|
|
165
|
-
});
|
|
166
|
-
return JSON.parse(resultJson);
|
|
167
|
-
} catch (error) {
|
|
168
|
-
throw new Error(`Audit execution failed: ${error}`);
|
|
168
|
+
const checks = {};
|
|
169
|
+
for (const name of PREMIUM_CHECK_NAMES) {
|
|
170
|
+
checks[name] = (html, url) => callSandboxCheck(name, html, url);
|
|
169
171
|
}
|
|
170
|
-
|
|
171
|
-
async function getTier() {
|
|
172
|
-
const wasm = await loadWasm();
|
|
173
|
-
return wasm.get_tier();
|
|
174
|
-
}
|
|
175
|
-
async function getChecksLimit() {
|
|
176
|
-
const wasm = await loadWasm();
|
|
177
|
-
return wasm.get_checks_limit();
|
|
178
|
-
}
|
|
179
|
-
async function validateLicense(licenseKey) {
|
|
180
|
-
const wasm = await loadWasm();
|
|
181
|
-
return wasm.validate_license(licenseKey);
|
|
172
|
+
return checks;
|
|
182
173
|
}
|
|
183
174
|
async function getVersion() {
|
|
184
175
|
const wasm = await loadWasm();
|
|
@@ -194,11 +185,8 @@ async function isReady() {
|
|
|
194
185
|
}
|
|
195
186
|
// Annotate the CommonJS export names for ESM import in node:
|
|
196
187
|
0 && (module.exports = {
|
|
197
|
-
|
|
198
|
-
getTier,
|
|
188
|
+
getPremiumChecks,
|
|
199
189
|
getVersion,
|
|
200
190
|
initEngine,
|
|
201
|
-
isReady
|
|
202
|
-
runAudit,
|
|
203
|
-
validateLicense
|
|
191
|
+
isReady
|
|
204
192
|
});
|
package/dist/wasm-engine.mjs
CHANGED
|
@@ -1,167 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import * as path from "path";
|
|
8
|
-
import * as fs from "fs";
|
|
9
|
-
var wasmModule = null;
|
|
10
|
-
var auditContext = null;
|
|
11
|
-
async function loadWasm() {
|
|
12
|
-
if (wasmModule) return wasmModule;
|
|
13
|
-
const possiblePaths = [
|
|
14
|
-
path.join(__dirname, "../native/pkg/rankcli_audit_engine.js"),
|
|
15
|
-
path.join(__dirname, "../wasm/rankcli_audit_engine.js"),
|
|
16
|
-
path.join(__dirname, "../../native/pkg/rankcli_audit_engine.js")
|
|
17
|
-
];
|
|
18
|
-
for (const wasmPath of possiblePaths) {
|
|
19
|
-
if (fs.existsSync(wasmPath)) {
|
|
20
|
-
wasmModule = __require(wasmPath);
|
|
21
|
-
return wasmModule;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
throw new Error(
|
|
25
|
-
`WASM engine not found. Run \`npm run build:wasm\` first.
|
|
26
|
-
Searched: ${possiblePaths.join(", ")}`
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
async function initEngine(licenseKey) {
|
|
30
|
-
const wasm = await loadWasm();
|
|
31
|
-
const key = licenseKey || process.env.RANKCLI_LICENSE_KEY || "";
|
|
32
|
-
try {
|
|
33
|
-
wasm.init_engine(key);
|
|
34
|
-
} catch (error) {
|
|
35
|
-
if (error instanceof Error && error.message.includes("Invalid license")) {
|
|
36
|
-
throw new Error("Invalid license key. Get one at https://rankcli.dev/pricing");
|
|
37
|
-
}
|
|
38
|
-
throw error;
|
|
39
|
-
}
|
|
40
|
-
const jsCode = wasm.get_decrypted_code();
|
|
41
|
-
createExecutionContext(jsCode);
|
|
42
|
-
}
|
|
43
|
-
function createExecutionContext(jsCode) {
|
|
44
|
-
const sandbox = {
|
|
45
|
-
// Console for debugging (can be disabled in production)
|
|
46
|
-
console: {
|
|
47
|
-
log: () => {
|
|
48
|
-
},
|
|
49
|
-
warn: () => {
|
|
50
|
-
},
|
|
51
|
-
error: () => {
|
|
52
|
-
}
|
|
53
|
-
},
|
|
54
|
-
// Timers (some checks may use setTimeout)
|
|
55
|
-
setTimeout,
|
|
56
|
-
clearTimeout,
|
|
57
|
-
// JSON parsing
|
|
58
|
-
JSON,
|
|
59
|
-
// Math operations
|
|
60
|
-
Math,
|
|
61
|
-
// String/Array operations
|
|
62
|
-
String,
|
|
63
|
-
Array,
|
|
64
|
-
Object,
|
|
65
|
-
RegExp,
|
|
66
|
-
Date,
|
|
67
|
-
// URL parsing
|
|
68
|
-
URL,
|
|
69
|
-
URLSearchParams,
|
|
70
|
-
// Node.js globals needed by bundled dependencies (e.g., cheerio)
|
|
71
|
-
Buffer,
|
|
72
|
-
process: { env: {} },
|
|
73
|
-
// TextEncoder/TextDecoder for string handling
|
|
74
|
-
TextEncoder,
|
|
75
|
-
TextDecoder,
|
|
76
|
-
// The RankCLI namespace will be populated by the code
|
|
77
|
-
RankCLI: {},
|
|
78
|
-
// Global reference
|
|
79
|
-
globalThis: {}
|
|
80
|
-
};
|
|
81
|
-
sandbox.globalThis = sandbox;
|
|
82
|
-
auditContext = vm.createContext(sandbox);
|
|
83
|
-
try {
|
|
84
|
-
vm.runInContext(jsCode, auditContext, {
|
|
85
|
-
filename: "rankcli-checks.js",
|
|
86
|
-
timeout: 5e3
|
|
87
|
-
// 5 second timeout for initialization
|
|
88
|
-
});
|
|
89
|
-
} catch (error) {
|
|
90
|
-
throw new Error(`Failed to initialize audit checks: ${error}`);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
async function runAudit(url, html) {
|
|
94
|
-
const wasm = await loadWasm();
|
|
95
|
-
if (!wasm.is_initialized()) {
|
|
96
|
-
throw new Error("Engine not initialized. Call initEngine() first.");
|
|
97
|
-
}
|
|
98
|
-
if (!auditContext) {
|
|
99
|
-
throw new Error("Execution context not ready");
|
|
100
|
-
}
|
|
101
|
-
const checksLimit = wasm.get_checks_limit();
|
|
102
|
-
const tier = wasm.get_tier();
|
|
103
|
-
try {
|
|
104
|
-
const script = new vm.Script(`
|
|
105
|
-
(function() {
|
|
106
|
-
const url = ${JSON.stringify(url)};
|
|
107
|
-
const html = ${JSON.stringify(html)};
|
|
108
|
-
const checksLimit = ${checksLimit};
|
|
109
|
-
|
|
110
|
-
if (typeof RankCLI.runAudit === 'function') {
|
|
111
|
-
return JSON.stringify(RankCLI.runAudit(url, html, checksLimit));
|
|
112
|
-
} else if (typeof RankCLI.runFullAudit === 'function') {
|
|
113
|
-
// Synchronous wrapper for async function
|
|
114
|
-
return JSON.stringify({
|
|
115
|
-
success: true,
|
|
116
|
-
score: 85,
|
|
117
|
-
issues: [],
|
|
118
|
-
checksRun: checksLimit,
|
|
119
|
-
tier: "${tier}"
|
|
120
|
-
});
|
|
121
|
-
} else {
|
|
122
|
-
throw new Error('RankCLI.runAudit not found in decrypted code');
|
|
123
|
-
}
|
|
124
|
-
})()
|
|
125
|
-
`);
|
|
126
|
-
const resultJson = script.runInContext(auditContext, {
|
|
127
|
-
timeout: 3e4
|
|
128
|
-
// 30 second timeout for audit
|
|
129
|
-
});
|
|
130
|
-
return JSON.parse(resultJson);
|
|
131
|
-
} catch (error) {
|
|
132
|
-
throw new Error(`Audit execution failed: ${error}`);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
async function getTier() {
|
|
136
|
-
const wasm = await loadWasm();
|
|
137
|
-
return wasm.get_tier();
|
|
138
|
-
}
|
|
139
|
-
async function getChecksLimit() {
|
|
140
|
-
const wasm = await loadWasm();
|
|
141
|
-
return wasm.get_checks_limit();
|
|
142
|
-
}
|
|
143
|
-
async function validateLicense(licenseKey) {
|
|
144
|
-
const wasm = await loadWasm();
|
|
145
|
-
return wasm.validate_license(licenseKey);
|
|
146
|
-
}
|
|
147
|
-
async function getVersion() {
|
|
148
|
-
const wasm = await loadWasm();
|
|
149
|
-
return wasm.version();
|
|
150
|
-
}
|
|
151
|
-
async function isReady() {
|
|
152
|
-
try {
|
|
153
|
-
const wasm = await loadWasm();
|
|
154
|
-
return wasm.is_initialized();
|
|
155
|
-
} catch {
|
|
156
|
-
return false;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
2
|
+
getPremiumChecks,
|
|
3
|
+
getVersion,
|
|
4
|
+
initEngine,
|
|
5
|
+
isReady
|
|
6
|
+
} from "./chunk-YX67Q5F6.mjs";
|
|
159
7
|
export {
|
|
160
|
-
|
|
161
|
-
getTier,
|
|
8
|
+
getPremiumChecks,
|
|
162
9
|
getVersion,
|
|
163
10
|
initEngine,
|
|
164
|
-
isReady
|
|
165
|
-
runAudit,
|
|
166
|
-
validateLicense
|
|
11
|
+
isReady
|
|
167
12
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rankcli/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.22",
|
|
4
4
|
"description": "RankCLI - Ship code, get ranked. SEO meets CI/CD.",
|
|
5
5
|
"homepage": "https://rankcli.dev",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"build:wasm:dev": "cd native && wasm-pack build --target nodejs --dev && mkdir -p ../wasm && cp -r pkg/* ../wasm/",
|
|
37
37
|
"prebuild:wasm": "mkdir -p wasm",
|
|
38
38
|
"inject-config": "node scripts/inject-config.js",
|
|
39
|
+
"build:sea": "bash scripts/build-sea.sh",
|
|
39
40
|
"prepublishOnly": "npm run build && npm run inject-config",
|
|
40
41
|
"start": "node dist/index.js",
|
|
41
42
|
"lint": "eslint src/",
|
|
@@ -44,7 +45,7 @@
|
|
|
44
45
|
"update-version": "node scripts/update-version.js"
|
|
45
46
|
},
|
|
46
47
|
"dependencies": {
|
|
47
|
-
"@rankcli/agent-runtime": "^0.0.
|
|
48
|
+
"@rankcli/agent-runtime": "^0.0.16",
|
|
48
49
|
"@supabase/supabase-js": "^2.45.0",
|
|
49
50
|
"chalk": "^5.3.0",
|
|
50
51
|
"commander": "^12.0.0",
|
|
@@ -59,6 +60,8 @@
|
|
|
59
60
|
"devDependencies": {
|
|
60
61
|
"@types/inquirer": "^9.0.0",
|
|
61
62
|
"@types/node": "^20.0.0",
|
|
63
|
+
"esbuild": "^0.28.2",
|
|
64
|
+
"postject": "^1.0.0-alpha.6",
|
|
62
65
|
"tsup": "^8.0.0",
|
|
63
66
|
"tsx": "^4.0.0",
|
|
64
67
|
"typescript": "^5.3.0",
|
package/dist/chunk-Y6FXYEAI.mjs
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
-
}) : x)(function(x) {
|
|
4
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
-
});
|
|
7
|
-
|
|
8
|
-
export {
|
|
9
|
-
__require
|
|
10
|
-
};
|