saasfunnels 0.4.0 → 0.4.2
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/README.md +4 -4
- package/dist/library.js +1 -1
- package/dist/saasfunnels.js +71 -11
- package/dist/types/identity.d.ts +1 -1
- package/dist/types/mcp.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -51,7 +51,7 @@ approved set is reviewable.
|
|
|
51
51
|
- uses: LeadEngine-ai/saasfunnels-cli/action@v0.2.1
|
|
52
52
|
with:
|
|
53
53
|
api-key: ${{ secrets.SAASFUNNELS_API_KEY }}
|
|
54
|
-
cli-version: 0.2.1
|
|
54
|
+
cli-version: 0.2.1 # pin it; a floating version rebaselines drift
|
|
55
55
|
discovery-roots: app,lib
|
|
56
56
|
```
|
|
57
57
|
|
|
@@ -63,8 +63,8 @@ measured against a comparable scan rather than a wider or narrower one.
|
|
|
63
63
|
Plan and pricing upload is opt-in:
|
|
64
64
|
|
|
65
65
|
```yaml
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
plan-sources: "true"
|
|
67
|
+
integration-id: <stripe integration id>
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
It runs only on the default branch, requires a committed
|
|
@@ -100,7 +100,7 @@ Support: support@saasfunnels.ai
|
|
|
100
100
|
|
|
101
101
|
Use `saasfunnels setup run` from the application repository after creating app credentials in Setup. Load `SAASFUNNELS_API_KEY` from **Developer setup access** into the local process environment. Its scopes are `developer:read` and `features:write`; it is not a browser or payment-administration key.
|
|
102
102
|
|
|
103
|
-
The command asks before uploading structured discovery proposals. `--send` provides explicit non-interactive approval. It coordinates features, pricing extraction, and plan branches, reports progress, and waits for the Stripe catalog. Run `saasfunnels setup run --resume` after an interruption. Existing accepted stages and customer review decisions are preserved.
|
|
103
|
+
The command asks before uploading structured discovery proposals. `--send` provides explicit non-interactive approval. It coordinates features, pricing extraction, and plan branches, reports progress, and waits for the Stripe catalog. Run `saasfunnels setup run --resume` after an interruption. Existing accepted stages and customer review decisions are preserved. Coverage questions include a SHA-256 fingerprint of their affected files so changed application behavior can reopen an answer while unrelated file changes keep it. Only the fingerprint and bounded paths are uploaded, never source contents.
|
|
104
104
|
|
|
105
105
|
The scanner reads supported application files one at a time without the old 500-file, 2 MB repository, or 200-candidate cutoffs. Generated code, tests, and symlinks are excluded. Files over 2 MB and uploads over the server limit stop with a specific recovery message.
|
|
106
106
|
|
package/dist/library.js
CHANGED
|
@@ -235858,7 +235858,7 @@ function validateFeatureCatalogManifest(value) {
|
|
|
235858
235858
|
|
|
235859
235859
|
// src/identity.ts
|
|
235860
235860
|
var SAASFUNNELS_CLI_NAME = "saasfunnels";
|
|
235861
|
-
var SAASFUNNELS_CLI_VERSION = "0.4.
|
|
235861
|
+
var SAASFUNNELS_CLI_VERSION = "0.4.2";
|
|
235862
235862
|
var SAASFUNNELS_PRODUCT_NAME = "SaaSFunnels";
|
|
235863
235863
|
var SAASFUNNELS_DEFAULT_API_BASE_URL = "https://app.saasfunnels.ai";
|
|
235864
235864
|
var SAASFUNNELS_ENV = Object.freeze({
|
package/dist/saasfunnels.js
CHANGED
|
@@ -233702,6 +233702,38 @@ function declarations(source, featuresOnly = false) {
|
|
|
233702
233702
|
true,
|
|
233703
233703
|
import_typescript2.default.ScriptKind.TSX
|
|
233704
233704
|
);
|
|
233705
|
+
const validatorBindings = /* @__PURE__ */ new Map();
|
|
233706
|
+
for (const statement of file2.statements) {
|
|
233707
|
+
if (!import_typescript2.default.isImportDeclaration(statement) || !import_typescript2.default.isStringLiteral(statement.moduleSpecifier) || !/^(?:zod(?:\/.*)?|yup|joi|valibot|superstruct)$/.test(
|
|
233708
|
+
statement.moduleSpecifier.text
|
|
233709
|
+
))
|
|
233710
|
+
continue;
|
|
233711
|
+
const clause = statement.importClause;
|
|
233712
|
+
if (clause?.name) validatorBindings.set(clause.name.text, "namespace");
|
|
233713
|
+
const bindings = clause?.namedBindings;
|
|
233714
|
+
if (bindings && import_typescript2.default.isNamespaceImport(bindings))
|
|
233715
|
+
validatorBindings.set(bindings.name.text, "namespace");
|
|
233716
|
+
if (bindings && import_typescript2.default.isNamedImports(bindings))
|
|
233717
|
+
for (const item of bindings.elements)
|
|
233718
|
+
validatorBindings.set(
|
|
233719
|
+
item.name.text,
|
|
233720
|
+
item.propertyName?.text ?? item.name.text
|
|
233721
|
+
);
|
|
233722
|
+
}
|
|
233723
|
+
const schemaConstructors = /^(?:object|strictObject|looseObject|record|array|tuple|union|intersection|discriminatedUnion|lazy|recursive|struct)$/;
|
|
233724
|
+
const schemaModifiers = /^(?:strict|passthrough|strip|optional|nullable|nullish|default|describe|meta|refine|superRefine|transform|pipe|readonly|brand|extend|merge|pick|omit|partial|required|min|max)$/;
|
|
233725
|
+
function validationSchema(node) {
|
|
233726
|
+
if (import_typescript2.default.isAsExpression(node) || import_typescript2.default.isSatisfiesExpression(node) || import_typescript2.default.isParenthesizedExpression(node))
|
|
233727
|
+
return validationSchema(node.expression);
|
|
233728
|
+
if (!import_typescript2.default.isCallExpression(node)) return false;
|
|
233729
|
+
const callee = node.expression;
|
|
233730
|
+
if (import_typescript2.default.isIdentifier(callee))
|
|
233731
|
+
return schemaConstructors.test(validatorBindings.get(callee.text) ?? "");
|
|
233732
|
+
if (!import_typescript2.default.isPropertyAccessExpression(callee)) return false;
|
|
233733
|
+
if (import_typescript2.default.isIdentifier(callee.expression) && validatorBindings.has(callee.expression.text) && schemaConstructors.test(callee.name.text))
|
|
233734
|
+
return true;
|
|
233735
|
+
return schemaModifiers.test(callee.name.text) && validationSchema(callee.expression);
|
|
233736
|
+
}
|
|
233705
233737
|
const values = /* @__PURE__ */ new Map();
|
|
233706
233738
|
const roots = [];
|
|
233707
233739
|
const planName = /^(plans?|pricing|tiers?|billingCatalog|planCatalog|subscriptionPlans)$/i;
|
|
@@ -233718,6 +233750,7 @@ function declarations(source, featuresOnly = false) {
|
|
|
233718
233750
|
for (const declaration of statement.declarationList.declarations) {
|
|
233719
233751
|
if (import_typescript2.default.isIdentifier(declaration.name) && declaration.initializer && isProductNode(declaration)) {
|
|
233720
233752
|
values.set(declaration.name.text, declaration.initializer);
|
|
233753
|
+
if (validationSchema(declaration.initializer)) continue;
|
|
233721
233754
|
if (planName.test(declaration.name.text) || containsAccess(declaration.initializer) && containsAccess(
|
|
233722
233755
|
declaration.initializer,
|
|
233723
233756
|
/^(prices|stripePriceId)$/
|
|
@@ -233725,7 +233758,7 @@ function declarations(source, featuresOnly = false) {
|
|
|
233725
233758
|
roots.push(declaration.initializer);
|
|
233726
233759
|
}
|
|
233727
233760
|
}
|
|
233728
|
-
if (import_typescript2.default.isExportAssignment(statement) && containsAccess(statement.expression))
|
|
233761
|
+
if (import_typescript2.default.isExportAssignment(statement) && containsAccess(statement.expression) && !validationSchema(statement.expression))
|
|
233729
233762
|
roots.push(statement.expression);
|
|
233730
233763
|
}
|
|
233731
233764
|
function read(node, depth = 0, locals = /* @__PURE__ */ new Map()) {
|
|
@@ -233928,7 +233961,7 @@ function extractSetupPricing(source, kind, catalogPrices = [], options = {}) {
|
|
|
233928
233961
|
|
|
233929
233962
|
// src/identity.ts
|
|
233930
233963
|
var SAASFUNNELS_CLI_NAME = "saasfunnels";
|
|
233931
|
-
var SAASFUNNELS_CLI_VERSION = "0.4.
|
|
233964
|
+
var SAASFUNNELS_CLI_VERSION = "0.4.2";
|
|
233932
233965
|
var SAASFUNNELS_PRODUCT_NAME = "SaaSFunnels";
|
|
233933
233966
|
var SAASFUNNELS_DEFAULT_API_BASE_URL = "https://app.saasfunnels.ai";
|
|
233934
233967
|
var SAASFUNNELS_ENV = Object.freeze({
|
|
@@ -233959,6 +233992,9 @@ function saasFunnelsPublicText(value) {
|
|
|
233959
233992
|
).replaceAll("prevenueFeatureKeys", "saasFunnelsFeatureKeys").replaceAll("PrevenueFeatureKey", "SaaSFunnelsFeatureKey").replaceAll("SaaSFunnels", SAASFUNNELS_PRODUCT_NAME);
|
|
233960
233993
|
}
|
|
233961
233994
|
|
|
233995
|
+
// src/discovery-coverage.ts
|
|
233996
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
233997
|
+
|
|
233962
233998
|
// src/plan-analysis.ts
|
|
233963
233999
|
var import_typescript3 = __toESM(require_typescript(), 1);
|
|
233964
234000
|
function effect(node) {
|
|
@@ -237287,9 +237323,10 @@ async function discoverCoverageLimitations(cwd2) {
|
|
|
237287
237323
|
);
|
|
237288
237324
|
const sources = /* @__PURE__ */ new Map();
|
|
237289
237325
|
const results = /* @__PURE__ */ new Map();
|
|
237326
|
+
const evidence = /* @__PURE__ */ new Map();
|
|
237290
237327
|
const record3 = (code, file2) => {
|
|
237291
237328
|
const paths = results.get(code) ?? /* @__PURE__ */ new Set();
|
|
237292
|
-
|
|
237329
|
+
paths.add(file2);
|
|
237293
237330
|
results.set(code, paths);
|
|
237294
237331
|
};
|
|
237295
237332
|
for (const path of files) {
|
|
@@ -237302,6 +237339,14 @@ async function discoverCoverageLimitations(cwd2) {
|
|
|
237302
237339
|
if (/\.(?:py|rb|go|rs|java|kt|cs|php|ex|exs|scala|swift|vue|svelte)$/.test(
|
|
237303
237340
|
path
|
|
237304
237341
|
)) {
|
|
237342
|
+
if (stat.size > 2e6)
|
|
237343
|
+
throw new Error(
|
|
237344
|
+
"Unsupported source exceeds the 2 MB scan limit. Narrow the discovery scope before retrying."
|
|
237345
|
+
);
|
|
237346
|
+
evidence.set(
|
|
237347
|
+
path,
|
|
237348
|
+
createHash3("sha256").update(await readFile3(join2(cwd2, path))).digest("hex")
|
|
237349
|
+
);
|
|
237305
237350
|
record3("unsupported_language", path);
|
|
237306
237351
|
continue;
|
|
237307
237352
|
}
|
|
@@ -237309,6 +237354,7 @@ async function discoverCoverageLimitations(cwd2) {
|
|
|
237309
237354
|
continue;
|
|
237310
237355
|
const source = await readFile3(join2(cwd2, path), "utf8");
|
|
237311
237356
|
sources.set(path, source);
|
|
237357
|
+
evidence.set(path, createHash3("sha256").update(source).digest("hex"));
|
|
237312
237358
|
const file2 = parseApplicationSource(path, source);
|
|
237313
237359
|
const visit = (node) => {
|
|
237314
237360
|
if (!isProductNode(node)) return;
|
|
@@ -237351,11 +237397,20 @@ async function discoverCoverageLimitations(cwd2) {
|
|
|
237351
237397
|
))
|
|
237352
237398
|
record3("dynamic_access", path);
|
|
237353
237399
|
}
|
|
237354
|
-
return [...results].map(([code, paths]) => ({
|
|
237400
|
+
return [...results].map(([code, paths]) => ({
|
|
237401
|
+
code,
|
|
237402
|
+
files: [...paths].sort().slice(0, 12),
|
|
237403
|
+
// Include all affected files in the receipt, even when the UI path list is capped.
|
|
237404
|
+
sourceFingerprint: createHash3("sha256").update(
|
|
237405
|
+
JSON.stringify(
|
|
237406
|
+
[...paths].sort().map((path) => [path, evidence.get(path)])
|
|
237407
|
+
)
|
|
237408
|
+
).digest("hex")
|
|
237409
|
+
}));
|
|
237355
237410
|
}
|
|
237356
237411
|
|
|
237357
237412
|
// src/setup-run.ts
|
|
237358
|
-
import { createHash as
|
|
237413
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
237359
237414
|
import { execFile as execFile4 } from "node:child_process";
|
|
237360
237415
|
import { promisify as promisify4 } from "node:util";
|
|
237361
237416
|
import { readFile as readFile6, writeFile as writeFile3, mkdir as mkdir3 } from "node:fs/promises";
|
|
@@ -237540,7 +237595,7 @@ function planMappingRequestKey(input) {
|
|
|
237540
237595
|
}
|
|
237541
237596
|
|
|
237542
237597
|
// src/plan-branches.ts
|
|
237543
|
-
import { createHash as
|
|
237598
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
237544
237599
|
import { readdir as readdir3, readFile as readFile5, lstat as lstat4 } from "node:fs/promises";
|
|
237545
237600
|
import { extname as extname3, join as join4, relative as relative3, resolve as resolve4, sep as sep2 } from "node:path";
|
|
237546
237601
|
var maxPlanBranchFileCharacters = 2e6;
|
|
@@ -237562,7 +237617,7 @@ function clusterPlanBranches(branches) {
|
|
|
237562
237617
|
const clusters = /* @__PURE__ */ new Map();
|
|
237563
237618
|
for (const branch of branches) {
|
|
237564
237619
|
const fullLocation = `${branch.repositoryPath}::${branch.symbol}`;
|
|
237565
|
-
const location = fullLocation.length <= 400 ? fullLocation : `${fullLocation.slice(0, 367)}~${
|
|
237620
|
+
const location = fullLocation.length <= 400 ? fullLocation : `${fullLocation.slice(0, 367)}~${createHash4("sha256").update(fullLocation).digest("hex").slice(0, 32)}`;
|
|
237566
237621
|
const key3 = `${branch.shape}|${location}`;
|
|
237567
237622
|
const existing = clusters.get(key3);
|
|
237568
237623
|
if (existing) existing.push(branch);
|
|
@@ -237652,7 +237707,7 @@ async function discoverPlanBranches(input) {
|
|
|
237652
237707
|
|
|
237653
237708
|
// src/setup-run.ts
|
|
237654
237709
|
var exec4 = promisify4(execFile4);
|
|
237655
|
-
var hash2 = (text) =>
|
|
237710
|
+
var hash2 = (text) => createHash5("sha256").update(text).digest("hex");
|
|
237656
237711
|
var contextSchema = external_exports.object({
|
|
237657
237712
|
contractVersion: external_exports.literal(3),
|
|
237658
237713
|
minimumCliVersion: external_exports.string(),
|
|
@@ -237704,6 +237759,10 @@ async function runGuidedSetup(options) {
|
|
|
237704
237759
|
...body === void 0 ? {} : { body: JSON.stringify(body) },
|
|
237705
237760
|
signal: AbortSignal.timeout(3e4)
|
|
237706
237761
|
});
|
|
237762
|
+
if (response.status === 426)
|
|
237763
|
+
throw new Error(
|
|
237764
|
+
"Setup request failed: update the installer with npx --yes saasfunnels@latest setup run"
|
|
237765
|
+
);
|
|
237707
237766
|
if (!response.ok)
|
|
237708
237767
|
throw new Error(
|
|
237709
237768
|
`Setup request failed (${response.status}). Check installation access and retry.`
|
|
@@ -237782,15 +237841,15 @@ async function runGuidedSetup(options) {
|
|
|
237782
237841
|
}
|
|
237783
237842
|
})
|
|
237784
237843
|
);
|
|
237785
|
-
const revision = `${head}:${hash2(JSON.stringify(["configuration-v3", diff, sourceHashes, reviewedInputs]))}`;
|
|
237844
|
+
const revision = `${head}:${hash2(JSON.stringify(["configuration-v3", SAASFUNNELS_CLI_VERSION, diff, sourceHashes, reviewedInputs]))}`;
|
|
237786
237845
|
const remote = (await exec4("git", ["remote", "get-url", "origin"], { cwd: options.cwd })).stdout.trim();
|
|
237787
237846
|
const repositoryKey = safeRepositoryKey(remote);
|
|
237788
237847
|
const statePath = join5(options.cwd, ".saasfunnels", "setup-run.json");
|
|
237789
237848
|
if (options.resume) {
|
|
237790
237849
|
const saved = JSON.parse(await readFile6(statePath, "utf8"));
|
|
237791
|
-
if (saved.generation !== context.generation || saved.repositoryKey !== repositoryKey || saved.revision !== revision)
|
|
237850
|
+
if (saved.cliVersion !== SAASFUNNELS_CLI_VERSION || saved.generation !== context.generation || saved.repositoryKey !== repositoryKey || saved.revision !== revision)
|
|
237792
237851
|
throw new Error(
|
|
237793
|
-
"The app connection or
|
|
237852
|
+
"The app connection, installer, or source changed. Start a fresh scan: npx --yes saasfunnels@latest setup run"
|
|
237794
237853
|
);
|
|
237795
237854
|
}
|
|
237796
237855
|
const approved = options.send || options.prompt && /^y(es)?$/i.test(
|
|
@@ -237815,6 +237874,7 @@ async function runGuidedSetup(options) {
|
|
|
237815
237874
|
statePath,
|
|
237816
237875
|
JSON.stringify(
|
|
237817
237876
|
{
|
|
237877
|
+
cliVersion: SAASFUNNELS_CLI_VERSION,
|
|
237818
237878
|
generation: context.generation,
|
|
237819
237879
|
repositoryKey,
|
|
237820
237880
|
revision,
|
package/dist/types/identity.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const SAASFUNNELS_CLI_NAME = "saasfunnels";
|
|
2
|
-
export declare const SAASFUNNELS_CLI_VERSION = "0.4.
|
|
2
|
+
export declare const SAASFUNNELS_CLI_VERSION = "0.4.2";
|
|
3
3
|
export declare const SAASFUNNELS_PRODUCT_NAME = "SaaSFunnels";
|
|
4
4
|
export declare const SAASFUNNELS_DEFAULT_API_BASE_URL = "https://app.saasfunnels.ai";
|
|
5
5
|
export declare const SAASFUNNELS_ENV: Readonly<{
|
package/dist/types/mcp.d.ts
CHANGED
|
@@ -77,7 +77,7 @@ export type SaaSFunnelsMcpOptions = {
|
|
|
77
77
|
output?: Pick<NodeJS.WritableStream, "write">;
|
|
78
78
|
};
|
|
79
79
|
declare const mcpProtocolVersion = "2025-06-18";
|
|
80
|
-
declare const mcpServerVersion = "0.4.
|
|
80
|
+
declare const mcpServerVersion = "0.4.2";
|
|
81
81
|
export declare function saasFunnelsMcpResources(): McpResourceDefinition[];
|
|
82
82
|
export declare function readSaaSFunnelsMcpResource(uri: string, options?: SaaSFunnelsMcpOptions): string;
|
|
83
83
|
export declare function saasFunnelsMcpToolDefinitions({ allowFunnelTests, allowFunnelWrites, allowTestWrites, includeHostedAccountTools, }?: {
|