@eduardbar/drift 1.3.0 → 1.4.0
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/.gga +50 -0
- package/.github/actions/drift-review/README.md +60 -0
- package/.github/actions/drift-review/action.yml +131 -0
- package/.github/actions/drift-scan/README.md +28 -32
- package/.github/actions/drift-scan/action.yml +78 -14
- package/.github/workflows/review-pr.yml +34 -41
- package/AGENTS.md +75 -251
- package/CHANGELOG.md +28 -0
- package/README.md +148 -41
- package/dist/benchmark.d.ts +1 -1
- package/dist/benchmark.js +71 -52
- package/dist/cli.js +243 -8
- package/dist/config.js +16 -2
- package/dist/diff.js +42 -50
- package/dist/doctor.d.ts +5 -0
- package/dist/doctor.js +133 -0
- package/dist/format.d.ts +17 -0
- package/dist/format.js +45 -0
- package/dist/guard-types.d.ts +57 -0
- package/dist/guard-types.js +2 -0
- package/dist/guard.d.ts +14 -0
- package/dist/guard.js +239 -0
- package/dist/index.d.ts +10 -3
- package/dist/index.js +4 -1
- package/dist/init.d.ts +15 -0
- package/dist/init.js +273 -0
- package/dist/map-cycles.d.ts +2 -0
- package/dist/map-cycles.js +34 -0
- package/dist/map-svg.d.ts +19 -0
- package/dist/map-svg.js +97 -0
- package/dist/map.js +78 -138
- package/dist/metrics.js +70 -55
- package/dist/output-metadata.d.ts +13 -0
- package/dist/output-metadata.js +17 -0
- package/dist/plugins-capabilities.d.ts +4 -0
- package/dist/plugins-capabilities.js +21 -0
- package/dist/plugins-messages.d.ts +10 -0
- package/dist/plugins-messages.js +16 -0
- package/dist/plugins-rules.d.ts +9 -0
- package/dist/plugins-rules.js +137 -0
- package/dist/plugins.d.ts +1 -1
- package/dist/plugins.js +45 -142
- package/dist/reporter-constants.d.ts +16 -0
- package/dist/reporter-constants.js +39 -0
- package/dist/reporter.d.ts +3 -3
- package/dist/reporter.js +35 -55
- package/dist/review.d.ts +2 -1
- package/dist/review.js +2 -1
- package/dist/rules/phase3-configurable.js +23 -15
- package/dist/saas/constants.d.ts +15 -0
- package/dist/saas/constants.js +48 -0
- package/dist/saas/dashboard.d.ts +8 -0
- package/dist/saas/dashboard.js +132 -0
- package/dist/saas/errors.d.ts +19 -0
- package/dist/saas/errors.js +37 -0
- package/dist/saas/helpers.d.ts +21 -0
- package/dist/saas/helpers.js +110 -0
- package/dist/saas/ingest.d.ts +3 -0
- package/dist/saas/ingest.js +249 -0
- package/dist/saas/organization.d.ts +5 -0
- package/dist/saas/organization.js +82 -0
- package/dist/saas/plan-change.d.ts +10 -0
- package/dist/saas/plan-change.js +15 -0
- package/dist/saas/store.d.ts +21 -0
- package/dist/saas/store.js +159 -0
- package/dist/saas/types.d.ts +191 -0
- package/dist/saas/types.js +2 -0
- package/dist/saas.d.ts +8 -218
- package/dist/saas.js +7 -761
- package/dist/sarif.d.ts +74 -0
- package/dist/sarif.js +122 -0
- package/dist/trust-advanced.d.ts +14 -0
- package/dist/trust-advanced.js +65 -0
- package/dist/trust-kpi-fs.d.ts +3 -0
- package/dist/trust-kpi-fs.js +141 -0
- package/dist/trust-kpi-parse.d.ts +7 -0
- package/dist/trust-kpi-parse.js +186 -0
- package/dist/trust-kpi-types.d.ts +16 -0
- package/dist/trust-kpi-types.js +2 -0
- package/dist/trust-kpi.d.ts +1 -3
- package/dist/trust-kpi.js +6 -266
- package/dist/trust-policy.d.ts +32 -0
- package/dist/trust-policy.js +160 -0
- package/dist/trust-render.d.ts +9 -0
- package/dist/trust-render.js +54 -0
- package/dist/trust-scoring.d.ts +9 -0
- package/dist/trust-scoring.js +208 -0
- package/dist/trust.d.ts +4 -32
- package/dist/trust.js +29 -432
- package/dist/types/app.d.ts +30 -0
- package/dist/types/app.js +2 -0
- package/dist/types/config.d.ts +25 -0
- package/dist/types/config.js +2 -0
- package/dist/types/core.d.ts +100 -0
- package/dist/types/core.js +2 -0
- package/dist/types/diff.d.ts +55 -0
- package/dist/types/diff.js +2 -0
- package/dist/types/plugin.d.ts +41 -0
- package/dist/types/plugin.js +2 -0
- package/dist/types/trust.d.ts +120 -0
- package/dist/types/trust.js +2 -0
- package/dist/types.d.ts +8 -365
- package/docs/release-notes-draft.md +40 -0
- package/docs/rules-catalog.md +49 -0
- package/docs/trust-core-release-checklist.md +37 -5
- package/package.json +3 -2
- package/packages/vscode-drift/src/code-actions.ts +1 -1
- package/schemas/drift-ai-output.v1.json +162 -0
- package/schemas/drift-report.v1.json +151 -0
- package/schemas/drift-trust.v1.json +131 -0
- package/scripts/smoke-repo.mjs +394 -0
- package/src/benchmark.ts +75 -53
- package/src/cli.ts +285 -13
- package/src/config.ts +19 -2
- package/src/diff.ts +57 -48
- package/src/doctor.ts +173 -0
- package/src/format.ts +81 -0
- package/src/guard-types.ts +64 -0
- package/src/guard.ts +324 -0
- package/src/index.ts +35 -0
- package/src/init.ts +298 -0
- package/src/map-cycles.ts +38 -0
- package/src/map-svg.ts +124 -0
- package/src/map.ts +111 -142
- package/src/metrics.ts +78 -59
- package/src/output-metadata.ts +30 -0
- package/src/plugins-capabilities.ts +36 -0
- package/src/plugins-messages.ts +35 -0
- package/src/plugins-rules.ts +296 -0
- package/src/plugins.ts +76 -283
- package/src/reporter-constants.ts +46 -0
- package/src/reporter.ts +64 -65
- package/src/review.ts +4 -2
- package/src/rules/phase3-configurable.ts +39 -26
- package/src/saas/constants.ts +56 -0
- package/src/saas/dashboard.ts +172 -0
- package/src/saas/errors.ts +45 -0
- package/src/saas/helpers.ts +140 -0
- package/src/saas/ingest.ts +278 -0
- package/src/saas/organization.ts +99 -0
- package/src/saas/plan-change.ts +19 -0
- package/src/saas/store.ts +172 -0
- package/src/saas/types.ts +216 -0
- package/src/saas.ts +49 -1031
- package/src/sarif.ts +232 -0
- package/src/trust-advanced.ts +99 -0
- package/src/trust-kpi-fs.ts +169 -0
- package/src/trust-kpi-parse.ts +219 -0
- package/src/trust-kpi-types.ts +19 -0
- package/src/trust-kpi.ts +8 -316
- package/src/trust-policy.ts +246 -0
- package/src/trust-render.ts +61 -0
- package/src/trust-scoring.ts +231 -0
- package/src/trust.ts +62 -576
- package/src/types/app.ts +30 -0
- package/src/types/config.ts +27 -0
- package/src/types/core.ts +105 -0
- package/src/types/diff.ts +61 -0
- package/src/types/plugin.ts +46 -0
- package/src/types/trust.ts +134 -0
- package/src/types.ts +78 -409
- package/tests/cli-sarif.test.ts +92 -0
- package/tests/format.test.ts +157 -0
- package/tests/new-features.test.ts +10 -2
- package/tests/phase1-init-doctor-guard.test.ts +199 -0
- package/tests/sarif.test.ts +160 -0
- package/tests/trust-kpi.test.ts +31 -4
- package/tests/trust.test.ts +18 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DriftIssue } from './types.js';
|
|
2
|
+
export declare const FIX_SUGGESTIONS: Record<string, string>;
|
|
3
|
+
export declare const RULE_EFFORT: Record<string, 'low' | 'medium' | 'high'>;
|
|
4
|
+
export declare const SEVERITY_ORDER: Record<string, number>;
|
|
5
|
+
export declare const EFFORT_ORDER: Record<string, number>;
|
|
6
|
+
export declare const AI_SIGNAL_RULES: Set<string>;
|
|
7
|
+
export declare const AI_CODE_SMELL_BOOST = 20;
|
|
8
|
+
export declare const AI_TRIGGER_LIMIT = 4;
|
|
9
|
+
export declare const AI_LIKELIHOOD_THRESHOLD = 35;
|
|
10
|
+
export declare const AI_SMELL_SCORE_MULTIPLIER = 15;
|
|
11
|
+
export declare const AI_SUSPECTED_LIMIT = 10;
|
|
12
|
+
export type DriftIssueWithFile = {
|
|
13
|
+
file: string;
|
|
14
|
+
issue: DriftIssue;
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=reporter-constants.d.ts.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export const FIX_SUGGESTIONS = {
|
|
2
|
+
'large-file': 'Consider splitting this file into smaller modules with single responsibility',
|
|
3
|
+
'large-function': 'Extract logic into smaller functions with descriptive names',
|
|
4
|
+
'debug-leftover': 'Remove this console.log or replace with proper logging library',
|
|
5
|
+
'dead-code': 'Remove unused import to keep code clean',
|
|
6
|
+
'duplicate-function-name': 'Consolidate with existing function or rename to clarify different behavior',
|
|
7
|
+
'any-abuse': "Replace 'any' with proper type definition",
|
|
8
|
+
'catch-swallow': 'Add error handling or logging in catch block',
|
|
9
|
+
'no-return-type': 'Add explicit return type for better type safety',
|
|
10
|
+
};
|
|
11
|
+
export const RULE_EFFORT = {
|
|
12
|
+
'debug-leftover': 'low',
|
|
13
|
+
'dead-code': 'low',
|
|
14
|
+
'no-return-type': 'low',
|
|
15
|
+
'any-abuse': 'medium',
|
|
16
|
+
'catch-swallow': 'medium',
|
|
17
|
+
'large-file': 'high',
|
|
18
|
+
'large-function': 'high',
|
|
19
|
+
'duplicate-function-name': 'high',
|
|
20
|
+
};
|
|
21
|
+
export const SEVERITY_ORDER = { error: 0, warning: 1, info: 2 };
|
|
22
|
+
export const EFFORT_ORDER = { low: 0, medium: 1, high: 2 };
|
|
23
|
+
export const AI_SIGNAL_RULES = new Set([
|
|
24
|
+
'over-commented',
|
|
25
|
+
'hardcoded-config',
|
|
26
|
+
'inconsistent-error-handling',
|
|
27
|
+
'unnecessary-abstraction',
|
|
28
|
+
'naming-inconsistency',
|
|
29
|
+
'comment-contradiction',
|
|
30
|
+
'promise-style-mix',
|
|
31
|
+
'any-abuse',
|
|
32
|
+
'ai-code-smell',
|
|
33
|
+
]);
|
|
34
|
+
export const AI_CODE_SMELL_BOOST = 20;
|
|
35
|
+
export const AI_TRIGGER_LIMIT = 4;
|
|
36
|
+
export const AI_LIKELIHOOD_THRESHOLD = 35;
|
|
37
|
+
export const AI_SMELL_SCORE_MULTIPLIER = 15;
|
|
38
|
+
export const AI_SUSPECTED_LIMIT = 10;
|
|
39
|
+
//# sourceMappingURL=reporter-constants.js.map
|
package/dist/reporter.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { FileReport, DriftReport,
|
|
2
|
-
export declare function buildReport(targetPath: string, files: FileReport[]):
|
|
1
|
+
import type { FileReport, DriftReport, AIOutputJson, DriftReportJson } from './types.js';
|
|
2
|
+
export declare function buildReport(targetPath: string, files: FileReport[]): DriftReportJson;
|
|
3
3
|
export declare function formatMarkdown(report: DriftReport): string;
|
|
4
|
-
export declare function formatAIOutput(report: DriftReport):
|
|
4
|
+
export declare function formatAIOutput(report: DriftReport): AIOutputJson;
|
|
5
5
|
//# sourceMappingURL=reporter.d.ts.map
|
package/dist/reporter.js
CHANGED
|
@@ -1,61 +1,34 @@
|
|
|
1
1
|
import { scoreToGradeText, severityIcon } from './utils.js';
|
|
2
2
|
import { computeRepoQuality, computeMaintenanceRisk } from './metrics.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
'debug-leftover': 'Remove this console.log or replace with proper logging library',
|
|
7
|
-
'dead-code': 'Remove unused import to keep code clean',
|
|
8
|
-
'duplicate-function-name': 'Consolidate with existing function or rename to clarify different behavior',
|
|
9
|
-
'any-abuse': "Replace 'any' with proper type definition",
|
|
10
|
-
'catch-swallow': 'Add error handling or logging in catch block',
|
|
11
|
-
'no-return-type': 'Add explicit return type for better type safety',
|
|
12
|
-
};
|
|
13
|
-
const RULE_EFFORT = {
|
|
14
|
-
'debug-leftover': 'low',
|
|
15
|
-
'dead-code': 'low',
|
|
16
|
-
'no-return-type': 'low',
|
|
17
|
-
'any-abuse': 'medium',
|
|
18
|
-
'catch-swallow': 'medium',
|
|
19
|
-
'large-file': 'high',
|
|
20
|
-
'large-function': 'high',
|
|
21
|
-
'duplicate-function-name': 'high',
|
|
22
|
-
};
|
|
23
|
-
const SEVERITY_ORDER = { error: 0, warning: 1, info: 2 };
|
|
24
|
-
const EFFORT_ORDER = { low: 0, medium: 1, high: 2 };
|
|
25
|
-
const AI_SIGNAL_RULES = new Set([
|
|
26
|
-
'over-commented',
|
|
27
|
-
'hardcoded-config',
|
|
28
|
-
'inconsistent-error-handling',
|
|
29
|
-
'unnecessary-abstraction',
|
|
30
|
-
'naming-inconsistency',
|
|
31
|
-
'comment-contradiction',
|
|
32
|
-
'promise-style-mix',
|
|
33
|
-
'any-abuse',
|
|
34
|
-
'ai-code-smell',
|
|
35
|
-
]);
|
|
36
|
-
export function buildReport(targetPath, files) {
|
|
37
|
-
const allIssues = files.flatMap((f) => f.issues);
|
|
3
|
+
import { OUTPUT_SCHEMA, withOutputMetadata } from './output-metadata.js';
|
|
4
|
+
import { AI_CODE_SMELL_BOOST, AI_LIKELIHOOD_THRESHOLD, AI_SIGNAL_RULES, AI_SMELL_SCORE_MULTIPLIER, AI_SUSPECTED_LIMIT, AI_TRIGGER_LIMIT, EFFORT_ORDER, FIX_SUGGESTIONS, RULE_EFFORT, SEVERITY_ORDER, } from './reporter-constants.js';
|
|
5
|
+
function summarizeIssues(allIssues) {
|
|
38
6
|
const byRule = {};
|
|
39
7
|
for (const issue of allIssues) {
|
|
40
8
|
byRule[issue.rule] = (byRule[issue.rule] ?? 0) + 1;
|
|
41
9
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
:
|
|
45
|
-
|
|
46
|
-
|
|
10
|
+
return {
|
|
11
|
+
errors: allIssues.filter((issue) => issue.severity === 'error').length,
|
|
12
|
+
warnings: allIssues.filter((issue) => issue.severity === 'warning').length,
|
|
13
|
+
infos: allIssues.filter((issue) => issue.severity === 'info').length,
|
|
14
|
+
byRule,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function calculateTotalScore(files) {
|
|
18
|
+
if (files.length === 0)
|
|
19
|
+
return 0;
|
|
20
|
+
return Math.round(files.reduce((sum, file) => sum + file.score, 0) / files.length);
|
|
21
|
+
}
|
|
22
|
+
function baseReportDefaults(summary, targetPath, files) {
|
|
23
|
+
const filesWithIssues = files.filter((file) => file.issues.length > 0).sort((a, b) => b.score - a.score);
|
|
24
|
+
const report = {
|
|
47
25
|
scannedAt: new Date().toISOString(),
|
|
48
26
|
targetPath,
|
|
49
|
-
files:
|
|
50
|
-
totalIssues:
|
|
51
|
-
totalScore,
|
|
27
|
+
files: filesWithIssues,
|
|
28
|
+
totalIssues: files.flatMap((file) => file.issues).length,
|
|
29
|
+
totalScore: calculateTotalScore(files),
|
|
52
30
|
totalFiles: files.length,
|
|
53
|
-
summary
|
|
54
|
-
errors: allIssues.filter((i) => i.severity === 'error').length,
|
|
55
|
-
warnings: allIssues.filter((i) => i.severity === 'warning').length,
|
|
56
|
-
infos: allIssues.filter((i) => i.severity === 'info').length,
|
|
57
|
-
byRule,
|
|
58
|
-
},
|
|
31
|
+
summary,
|
|
59
32
|
quality: {
|
|
60
33
|
overall: 100,
|
|
61
34
|
dimensions: {
|
|
@@ -76,6 +49,12 @@ export function buildReport(targetPath, files) {
|
|
|
76
49
|
},
|
|
77
50
|
},
|
|
78
51
|
};
|
|
52
|
+
return withOutputMetadata(report, OUTPUT_SCHEMA.report);
|
|
53
|
+
}
|
|
54
|
+
export function buildReport(targetPath, files) {
|
|
55
|
+
const allIssues = files.flatMap((f) => f.issues);
|
|
56
|
+
const summary = summarizeIssues(allIssues);
|
|
57
|
+
const baseReport = baseReportDefaults(summary, targetPath, files);
|
|
79
58
|
baseReport.quality = computeRepoQuality(targetPath, files);
|
|
80
59
|
baseReport.maintenanceRisk = computeMaintenanceRisk(baseReport);
|
|
81
60
|
return baseReport;
|
|
@@ -191,12 +170,12 @@ function fileAILikelihood(fileIssues) {
|
|
|
191
170
|
triggerCounts.set(issue.rule, (triggerCounts.get(issue.rule) ?? 0) + 1);
|
|
192
171
|
}
|
|
193
172
|
const triggerTotal = [...triggerCounts.values()].reduce((sum, count) => sum + count, 0);
|
|
194
|
-
const smellBoost = fileIssues.some((issue) => issue.rule === 'ai-code-smell') ?
|
|
173
|
+
const smellBoost = fileIssues.some((issue) => issue.rule === 'ai-code-smell') ? AI_CODE_SMELL_BOOST : 0;
|
|
195
174
|
const ratioScore = Math.round((triggerTotal / Math.max(fileIssues.length, 1)) * 100);
|
|
196
175
|
const score = Math.max(0, Math.min(100, ratioScore + smellBoost));
|
|
197
176
|
const triggers = [...triggerCounts.entries()]
|
|
198
177
|
.sort((a, b) => b[1] - a[1])
|
|
199
|
-
.slice(0,
|
|
178
|
+
.slice(0, AI_TRIGGER_LIMIT)
|
|
200
179
|
.map(([rule]) => rule);
|
|
201
180
|
return { score, triggers };
|
|
202
181
|
}
|
|
@@ -210,16 +189,16 @@ function computeAILikelihood(report) {
|
|
|
210
189
|
triggers: likelihood.triggers,
|
|
211
190
|
};
|
|
212
191
|
})
|
|
213
|
-
.filter((entry) => entry.ai_likelihood >=
|
|
192
|
+
.filter((entry) => entry.ai_likelihood >= AI_LIKELIHOOD_THRESHOLD)
|
|
214
193
|
.sort((a, b) => b.ai_likelihood - a.ai_likelihood);
|
|
215
194
|
const overall = suspected.length === 0
|
|
216
195
|
? 0
|
|
217
196
|
: Math.round(suspected.reduce((sum, entry) => sum + entry.ai_likelihood, 0) / suspected.length);
|
|
218
197
|
const smellCount = report.files.flatMap((file) => file.issues).filter((issue) => issue.rule === 'ai-code-smell').length;
|
|
219
|
-
const smellScore = Math.min(100, smellCount *
|
|
198
|
+
const smellScore = Math.min(100, smellCount * AI_SMELL_SCORE_MULTIPLIER);
|
|
220
199
|
return {
|
|
221
200
|
overall,
|
|
222
|
-
files: suspected.slice(0,
|
|
201
|
+
files: suspected.slice(0, AI_SUSPECTED_LIMIT),
|
|
223
202
|
smellScore,
|
|
224
203
|
};
|
|
225
204
|
}
|
|
@@ -230,7 +209,7 @@ export function formatAIOutput(report) {
|
|
|
230
209
|
const rulesDetected = [...new Set(allIssues.map((i) => i.issue.rule))];
|
|
231
210
|
const grade = scoreToGradeText(report.totalScore);
|
|
232
211
|
const aiLikelihood = computeAILikelihood(report);
|
|
233
|
-
|
|
212
|
+
const output = {
|
|
234
213
|
summary: {
|
|
235
214
|
score: report.totalScore,
|
|
236
215
|
grade: grade.label.toUpperCase(),
|
|
@@ -251,5 +230,6 @@ export function formatAIOutput(report) {
|
|
|
251
230
|
recommended_action: buildRecommendedAction(priorityOrder),
|
|
252
231
|
},
|
|
253
232
|
};
|
|
233
|
+
return withOutputMetadata(output, OUTPUT_SCHEMA.ai);
|
|
254
234
|
}
|
|
255
235
|
//# sourceMappingURL=reporter.js.map
|
package/dist/review.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DriftDiff } from './types.js';
|
|
2
|
-
|
|
2
|
+
interface DriftReview {
|
|
3
3
|
baseRef: string;
|
|
4
4
|
scannedAt: string;
|
|
5
5
|
totalDelta: number;
|
|
@@ -12,4 +12,5 @@ export interface DriftReview {
|
|
|
12
12
|
}
|
|
13
13
|
export declare function formatReviewMarkdown(review: DriftReview): string;
|
|
14
14
|
export declare function generateReview(projectPath: string, baseRef: string): Promise<DriftReview>;
|
|
15
|
+
export {};
|
|
15
16
|
//# sourceMappingURL=review.d.ts.map
|
package/dist/review.js
CHANGED
|
@@ -4,10 +4,11 @@ import { loadConfig } from './config.js';
|
|
|
4
4
|
import { buildReport } from './reporter.js';
|
|
5
5
|
import { cleanupTempDir, extractFilesAtRef } from './git.js';
|
|
6
6
|
import { computeDiff } from './diff.js';
|
|
7
|
+
const REVIEW_TOP_FILES_LIMIT = 8;
|
|
7
8
|
export function formatReviewMarkdown(review) {
|
|
8
9
|
const trendIcon = review.status === 'regressed' ? '⚠️' : review.status === 'improved' ? '✅' : 'ℹ️';
|
|
9
10
|
const topFiles = review.diff.files
|
|
10
|
-
.slice(0,
|
|
11
|
+
.slice(0, REVIEW_TOP_FILES_LIMIT)
|
|
11
12
|
.map((file) => {
|
|
12
13
|
const sign = file.scoreDelta > 0 ? '+' : '';
|
|
13
14
|
return `- \`${file.path}\`: ${file.scoreBefore} -> ${file.scoreAfter} (${sign}${file.scoreDelta}), +${file.newIssues.length} new / -${file.resolvedIssues.length} resolved`;
|
|
@@ -19,11 +19,13 @@ const HTTP_IMPORT_PATTERNS = [
|
|
|
19
19
|
];
|
|
20
20
|
function isControllerFile(filePath) {
|
|
21
21
|
const normalized = filePath.replace(/\\/g, '/').toLowerCase();
|
|
22
|
-
|
|
22
|
+
const segments = normalized.split('/');
|
|
23
|
+
return segments.includes('controller') || segments.includes('controllers') || normalized.endsWith('controller.ts') || normalized.endsWith('controller.js');
|
|
23
24
|
}
|
|
24
25
|
function isServiceFile(filePath) {
|
|
25
26
|
const normalized = filePath.replace(/\\/g, '/').toLowerCase();
|
|
26
|
-
|
|
27
|
+
const segments = normalized.split('/');
|
|
28
|
+
return segments.includes('service') || segments.includes('services') || normalized.endsWith('service.ts') || normalized.endsWith('service.js');
|
|
27
29
|
}
|
|
28
30
|
function createIssue(rule, message, line, snippet) {
|
|
29
31
|
return {
|
|
@@ -74,24 +76,30 @@ export function detectMaxFunctionLines(file, config) {
|
|
|
74
76
|
if (!maxLines || maxLines <= 0)
|
|
75
77
|
return [];
|
|
76
78
|
const issues = [];
|
|
79
|
+
collectFunctionLineIssues(file, maxLines, issues);
|
|
80
|
+
collectMethodLineIssues(file, maxLines, issues);
|
|
81
|
+
return issues;
|
|
82
|
+
}
|
|
83
|
+
function countBodyLines(body) {
|
|
84
|
+
if (!body)
|
|
85
|
+
return 0;
|
|
86
|
+
return body.getEndLineNumber() - body.getStartLineNumber() - 1;
|
|
87
|
+
}
|
|
88
|
+
function collectFunctionLineIssues(file, maxLines, issues) {
|
|
77
89
|
for (const fn of file.getFunctions()) {
|
|
78
|
-
const
|
|
79
|
-
if (
|
|
90
|
+
const lines = countBodyLines(fn.getBody());
|
|
91
|
+
if (lines <= maxLines)
|
|
80
92
|
continue;
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
issues.push(createIssue('max-function-lines', `Function '${fn.getName() ?? '(anonymous)'}' has ${lines} lines (max: ${maxLines}).`, fn.getStartLineNumber(), fn.getName() ?? '(anonymous)'));
|
|
84
|
-
}
|
|
93
|
+
const functionName = fn.getName() ?? '(anonymous)';
|
|
94
|
+
issues.push(createIssue('max-function-lines', `Function '${functionName}' has ${lines} lines (max: ${maxLines}).`, fn.getStartLineNumber(), functionName));
|
|
85
95
|
}
|
|
96
|
+
}
|
|
97
|
+
function collectMethodLineIssues(file, maxLines, issues) {
|
|
86
98
|
for (const method of file.getDescendantsOfKind(SyntaxKind.MethodDeclaration)) {
|
|
87
|
-
const
|
|
88
|
-
if (
|
|
99
|
+
const lines = countBodyLines(method.getBody());
|
|
100
|
+
if (lines <= maxLines)
|
|
89
101
|
continue;
|
|
90
|
-
|
|
91
|
-
if (lines > maxLines) {
|
|
92
|
-
issues.push(createIssue('max-function-lines', `Method '${method.getName()}' has ${lines} lines (max: ${maxLines}).`, method.getStartLineNumber(), method.getName()));
|
|
93
|
-
}
|
|
102
|
+
issues.push(createIssue('max-function-lines', `Method '${method.getName()}' has ${lines} lines (max: ${maxLines}).`, method.getStartLineNumber(), method.getName()));
|
|
94
103
|
}
|
|
95
|
-
return issues;
|
|
96
104
|
}
|
|
97
105
|
//# sourceMappingURL=phase3-configurable.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { SaasOperation, SaasPlan, SaasPolicy, SaasRole } from './types.js';
|
|
2
|
+
export declare const STORE_VERSION = 3;
|
|
3
|
+
export declare const ACTIVE_WINDOW_DAYS = 30;
|
|
4
|
+
export declare const DEFAULT_ORGANIZATION_ID = "default-org";
|
|
5
|
+
export declare const DASHBOARD_REPO_LIMIT = 15;
|
|
6
|
+
export declare const DASHBOARD_BAR_UNIT = 8;
|
|
7
|
+
export declare const DASHBOARD_BAR_MIN_WIDTH = 8;
|
|
8
|
+
export declare const VALID_ROLES: SaasRole[];
|
|
9
|
+
export declare const VALID_PLANS: SaasPlan[];
|
|
10
|
+
export declare const ROLE_PRIORITY: Record<SaasRole, number>;
|
|
11
|
+
export declare const REQUIRED_ROLE_BY_OPERATION: Record<SaasOperation, SaasRole>;
|
|
12
|
+
export declare const DEFAULT_SAAS_POLICY: SaasPolicy;
|
|
13
|
+
export declare function daysAgo(days: number): number;
|
|
14
|
+
export declare function createRandomId(prefix: string): string;
|
|
15
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export const STORE_VERSION = 3;
|
|
2
|
+
export const ACTIVE_WINDOW_DAYS = 30;
|
|
3
|
+
export const DEFAULT_ORGANIZATION_ID = 'default-org';
|
|
4
|
+
const HOURS_PER_DAY = 24;
|
|
5
|
+
const MINUTES_PER_HOUR = 60;
|
|
6
|
+
const SECONDS_PER_MINUTE = 60;
|
|
7
|
+
const MILLISECONDS_PER_SECOND = 1000;
|
|
8
|
+
const RANDOM_ID_RADIX = 16;
|
|
9
|
+
const RANDOM_ID_START = 2;
|
|
10
|
+
const RANDOM_ID_END = 10;
|
|
11
|
+
export const DASHBOARD_REPO_LIMIT = 15;
|
|
12
|
+
export const DASHBOARD_BAR_UNIT = 8;
|
|
13
|
+
export const DASHBOARD_BAR_MIN_WIDTH = 8;
|
|
14
|
+
export const VALID_ROLES = ['owner', 'member', 'viewer'];
|
|
15
|
+
export const VALID_PLANS = ['free', 'sponsor', 'team', 'business'];
|
|
16
|
+
export const ROLE_PRIORITY = {
|
|
17
|
+
viewer: 1,
|
|
18
|
+
member: 2,
|
|
19
|
+
owner: 3,
|
|
20
|
+
};
|
|
21
|
+
export const REQUIRED_ROLE_BY_OPERATION = {
|
|
22
|
+
'snapshot:write': 'member',
|
|
23
|
+
'snapshot:read': 'viewer',
|
|
24
|
+
'summary:read': 'viewer',
|
|
25
|
+
'billing:write': 'owner',
|
|
26
|
+
'billing:read': 'viewer',
|
|
27
|
+
};
|
|
28
|
+
export const DEFAULT_SAAS_POLICY = {
|
|
29
|
+
freeUserThreshold: 7500,
|
|
30
|
+
maxRunsPerWorkspacePerMonth: 500,
|
|
31
|
+
maxReposPerWorkspace: 20,
|
|
32
|
+
retentionDays: 90,
|
|
33
|
+
strictActorEnforcement: false,
|
|
34
|
+
maxWorkspacesPerOrganizationByPlan: {
|
|
35
|
+
free: 20,
|
|
36
|
+
sponsor: 50,
|
|
37
|
+
team: 200,
|
|
38
|
+
business: 1000,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
export function daysAgo(days) {
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
return now - days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MILLISECONDS_PER_SECOND;
|
|
44
|
+
}
|
|
45
|
+
export function createRandomId(prefix) {
|
|
46
|
+
return `${prefix}-${Math.random().toString(RANDOM_ID_RADIX).slice(RANDOM_ID_START, RANDOM_ID_END)}`;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { SaasPolicyOverrides, SaasQueryOptions, SaasSnapshot, SaasSummary } from './types.js';
|
|
2
|
+
export declare function listSaasSnapshots(options?: SaasQueryOptions): SaasSnapshot[];
|
|
3
|
+
export declare function getSaasSummary(options?: SaasQueryOptions): SaasSummary;
|
|
4
|
+
export declare function generateSaasDashboardHtml(options?: {
|
|
5
|
+
storeFile?: string;
|
|
6
|
+
policy?: SaasPolicyOverrides;
|
|
7
|
+
}): string;
|
|
8
|
+
//# sourceMappingURL=dashboard.d.ts.map
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { DASHBOARD_BAR_MIN_WIDTH, DASHBOARD_BAR_UNIT, DASHBOARD_REPO_LIMIT } from './constants.js';
|
|
3
|
+
import { assertPermissionInStore, defaultSaasStorePath, loadStoreInternal, saveStore } from './store.js';
|
|
4
|
+
import { DEFAULT_ORGANIZATION_ID, computeRunsPerMonth, computeUsersRegistered, escapeHtml, isRepoActive, isWorkspaceActive, matchesRepoScope, matchesTenantScope, matchesWorkspaceScope, } from './helpers.js';
|
|
5
|
+
function assertSummaryReadPermission(store, options) {
|
|
6
|
+
const shouldEnforceActorForScope = store.policy.strictActorEnforcement && Boolean(options?.organizationId || options?.workspaceId);
|
|
7
|
+
if (!options?.actorUserId && !shouldEnforceActorForScope)
|
|
8
|
+
return;
|
|
9
|
+
const organizationId = options?.organizationId ?? DEFAULT_ORGANIZATION_ID;
|
|
10
|
+
assertPermissionInStore(store, {
|
|
11
|
+
operation: 'summary:read',
|
|
12
|
+
organizationId,
|
|
13
|
+
workspaceId: options?.workspaceId,
|
|
14
|
+
actorUserId: options?.actorUserId,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
function buildWorkspaceStats(store) {
|
|
18
|
+
return Object.values(store.workspaces)
|
|
19
|
+
.map((workspace) => {
|
|
20
|
+
const snapshots = store.snapshots.filter((snapshot) => snapshot.organizationId === workspace.organizationId && snapshot.workspaceId === workspace.id);
|
|
21
|
+
const runs = snapshots.length;
|
|
22
|
+
const avgScore = runs === 0 ? 0 : Math.round(snapshots.reduce((sum, snapshot) => sum + snapshot.totalScore, 0) / runs);
|
|
23
|
+
const lastRun = snapshots.sort((a, b) => b.createdAt.localeCompare(a.createdAt))[0]?.createdAt ?? 'n/a';
|
|
24
|
+
return {
|
|
25
|
+
organizationId: workspace.organizationId,
|
|
26
|
+
id: workspace.id,
|
|
27
|
+
runs,
|
|
28
|
+
avgScore,
|
|
29
|
+
lastRun,
|
|
30
|
+
};
|
|
31
|
+
})
|
|
32
|
+
.sort((a, b) => b.avgScore - a.avgScore);
|
|
33
|
+
}
|
|
34
|
+
function buildRepoStats(store) {
|
|
35
|
+
return Object.values(store.repos)
|
|
36
|
+
.map((repo) => {
|
|
37
|
+
const snapshots = store.snapshots.filter((snapshot) => snapshot.repoId === repo.id);
|
|
38
|
+
const runs = snapshots.length;
|
|
39
|
+
const avgScore = runs === 0 ? 0 : Math.round(snapshots.reduce((sum, snapshot) => sum + snapshot.totalScore, 0) / runs);
|
|
40
|
+
return {
|
|
41
|
+
workspaceId: repo.workspaceId,
|
|
42
|
+
name: repo.name,
|
|
43
|
+
runs,
|
|
44
|
+
avgScore,
|
|
45
|
+
};
|
|
46
|
+
})
|
|
47
|
+
.sort((a, b) => b.avgScore - a.avgScore)
|
|
48
|
+
.slice(0, DASHBOARD_REPO_LIMIT);
|
|
49
|
+
}
|
|
50
|
+
function buildRunsRows(summary) {
|
|
51
|
+
return Object.entries(summary.runsPerMonth)
|
|
52
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
53
|
+
.map(([month, count]) => {
|
|
54
|
+
const width = Math.max(DASHBOARD_BAR_MIN_WIDTH, count * DASHBOARD_BAR_UNIT);
|
|
55
|
+
return `<tr><td>${escapeHtml(month)}</td><td>${count}</td><td><div class="bar" style="width:${width}px"></div></td></tr>`;
|
|
56
|
+
})
|
|
57
|
+
.join('');
|
|
58
|
+
}
|
|
59
|
+
function buildWorkspaceRows(workspaceStats) {
|
|
60
|
+
return workspaceStats
|
|
61
|
+
.map((workspace) => `<tr><td>${escapeHtml(workspace.organizationId)}</td><td>${escapeHtml(workspace.id)}</td><td>${workspace.runs}</td><td>${workspace.avgScore}</td><td>${escapeHtml(workspace.lastRun)}</td></tr>`)
|
|
62
|
+
.join('');
|
|
63
|
+
}
|
|
64
|
+
function buildRepoRows(repoStats) {
|
|
65
|
+
return repoStats
|
|
66
|
+
.map((repo) => `<tr><td>${escapeHtml(repo.workspaceId)}</td><td>${escapeHtml(repo.name)}</td><td>${repo.runs}</td><td>${repo.avgScore}</td></tr>`)
|
|
67
|
+
.join('');
|
|
68
|
+
}
|
|
69
|
+
function renderDashboardHtmlDocument(input) {
|
|
70
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>drift cloud dashboard</title><style>:root { color-scheme: light; } body { margin: 0; font-family: "Segoe UI", Arial, sans-serif; background: #f4f7fb; color: #0f172a; } main { max-width: 980px; margin: 0 auto; padding: 24px; } h1 { margin: 0 0 6px; } p.meta { margin: 0 0 20px; color: #475569; } .cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin-bottom: 18px; } .card { background: #ffffff; border-radius: 10px; padding: 14px; border: 1px solid #dbe3ef; } .card .label { font-size: 12px; color: #64748b; text-transform: uppercase; letter-spacing: 0.08em; } .card .value { font-size: 26px; font-weight: 700; margin-top: 4px; } table { width: 100%; border-collapse: collapse; margin-top: 10px; background: #ffffff; border: 1px solid #dbe3ef; border-radius: 10px; overflow: hidden; } th, td { padding: 10px; border-bottom: 1px solid #e2e8f0; text-align: left; font-size: 14px; } th { background: #eef2f9; } .section { margin-top: 18px; } .bar { height: 10px; background: linear-gradient(90deg, #0ea5e9, #22c55e); border-radius: 999px; } .pill { display: inline-block; border-radius: 999px; padding: 4px 10px; font-size: 12px; font-weight: 600; } .pill.free { background: #dcfce7; color: #166534; } .pill.paid { background: #fee2e2; color: #991b1b; }</style></head><body><main><h1>drift cloud dashboard</h1><p class="meta">Store: ${escapeHtml(input.storeFile)}</p><div class="cards"><div class="card"><div class="label">Plan Phase</div><div class="value"><span class="pill ${input.summary.phase}">${input.summary.phase.toUpperCase()}</span></div></div><div class="card"><div class="label">Users</div><div class="value">${input.summary.usersRegistered}</div></div><div class="card"><div class="label">Active Workspaces</div><div class="value">${input.summary.workspacesActive}</div></div><div class="card"><div class="label">Active Repos</div><div class="value">${input.summary.reposActive}</div></div><div class="card"><div class="label">Snapshots</div><div class="value">${input.summary.totalSnapshots}</div></div><div class="card"><div class="label">Free Seats Left</div><div class="value">${input.summary.freeUsersRemaining}</div></div></div><section class="section"><h2>Runs Per Month</h2><table><thead><tr><th>Month</th><th>Runs</th><th>Trend</th></tr></thead><tbody>${input.runsRows || '<tr><td colspan="3">No runs yet</td></tr>'}</tbody></table></section><section class="section"><h2>Workspace Hotspots</h2><table><thead><tr><th>Organization</th><th>Workspace</th><th>Runs</th><th>Avg Score</th><th>Last Run</th></tr></thead><tbody>${input.workspaceRows || '<tr><td colspan="5">No workspace data</td></tr>'}</tbody></table></section><section class="section"><h2>Repo Hotspots</h2><table><thead><tr><th>Workspace</th><th>Repo</th><th>Runs</th><th>Avg Score</th></tr></thead><tbody>${input.repoRows || '<tr><td colspan="4">No repo data</td></tr>'}</tbody></table></section></main></body></html>`;
|
|
71
|
+
}
|
|
72
|
+
export function listSaasSnapshots(options) {
|
|
73
|
+
const storeFile = resolve(options?.storeFile ?? defaultSaasStorePath());
|
|
74
|
+
const store = loadStoreInternal(storeFile, options?.policy);
|
|
75
|
+
const shouldEnforceActorForScope = store.policy.strictActorEnforcement && Boolean(options?.organizationId || options?.workspaceId);
|
|
76
|
+
if (options?.actorUserId || shouldEnforceActorForScope) {
|
|
77
|
+
const organizationId = options?.organizationId ?? DEFAULT_ORGANIZATION_ID;
|
|
78
|
+
assertPermissionInStore(store, {
|
|
79
|
+
operation: 'snapshot:read',
|
|
80
|
+
organizationId,
|
|
81
|
+
workspaceId: options?.workspaceId,
|
|
82
|
+
actorUserId: options?.actorUserId,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
saveStore(storeFile, store);
|
|
86
|
+
return store.snapshots
|
|
87
|
+
.filter((snapshot) => matchesTenantScope(snapshot, options))
|
|
88
|
+
.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
89
|
+
}
|
|
90
|
+
export function getSaasSummary(options) {
|
|
91
|
+
const storeFile = resolve(options?.storeFile ?? defaultSaasStorePath());
|
|
92
|
+
const store = loadStoreInternal(storeFile, options?.policy);
|
|
93
|
+
assertSummaryReadPermission(store, options);
|
|
94
|
+
saveStore(storeFile, store);
|
|
95
|
+
const scopedSnapshots = store.snapshots.filter((snapshot) => matchesTenantScope(snapshot, options));
|
|
96
|
+
const scopedWorkspaces = Object.values(store.workspaces).filter((workspace) => matchesWorkspaceScope(workspace, options));
|
|
97
|
+
const scopedRepos = Object.values(store.repos).filter((repo) => matchesRepoScope(repo, options));
|
|
98
|
+
const usersRegistered = computeUsersRegistered(store, scopedSnapshots, options);
|
|
99
|
+
const workspacesActive = scopedWorkspaces.filter((workspace) => isWorkspaceActive(workspace)).length;
|
|
100
|
+
const reposActive = scopedRepos.filter((repo) => isRepoActive(repo)).length;
|
|
101
|
+
const runsPerMonth = computeRunsPerMonth(scopedSnapshots);
|
|
102
|
+
const thresholdReached = usersRegistered >= store.policy.freeUserThreshold;
|
|
103
|
+
return {
|
|
104
|
+
policy: store.policy,
|
|
105
|
+
usersRegistered,
|
|
106
|
+
workspacesActive,
|
|
107
|
+
reposActive,
|
|
108
|
+
runsPerMonth,
|
|
109
|
+
totalSnapshots: scopedSnapshots.length,
|
|
110
|
+
phase: thresholdReached ? 'paid' : 'free',
|
|
111
|
+
thresholdReached,
|
|
112
|
+
freeUsersRemaining: Math.max(0, store.policy.freeUserThreshold - usersRegistered),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
export function generateSaasDashboardHtml(options) {
|
|
116
|
+
const storeFile = resolve(options?.storeFile ?? defaultSaasStorePath());
|
|
117
|
+
const store = loadStoreInternal(storeFile, options?.policy);
|
|
118
|
+
const summary = getSaasSummary(options);
|
|
119
|
+
const workspaceStats = buildWorkspaceStats(store);
|
|
120
|
+
const repoStats = buildRepoStats(store);
|
|
121
|
+
const runsRows = buildRunsRows(summary);
|
|
122
|
+
const workspaceRows = buildWorkspaceRows(workspaceStats);
|
|
123
|
+
const repoRows = buildRepoRows(repoStats);
|
|
124
|
+
return renderDashboardHtmlDocument({
|
|
125
|
+
storeFile,
|
|
126
|
+
summary,
|
|
127
|
+
runsRows,
|
|
128
|
+
workspaceRows,
|
|
129
|
+
repoRows,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=dashboard.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SaasOperation, SaasPermissionContext, SaasRole } from './types.js';
|
|
2
|
+
export declare class SaasPermissionError extends Error {
|
|
3
|
+
readonly code = "SAAS_PERMISSION_DENIED";
|
|
4
|
+
readonly operation: SaasOperation;
|
|
5
|
+
readonly organizationId: string;
|
|
6
|
+
readonly workspaceId?: string;
|
|
7
|
+
readonly actorUserId?: string;
|
|
8
|
+
readonly requiredRole: SaasRole;
|
|
9
|
+
readonly actorRole?: SaasRole;
|
|
10
|
+
constructor(context: SaasPermissionContext, requiredRole: SaasRole, actorRole?: SaasRole);
|
|
11
|
+
}
|
|
12
|
+
export declare class SaasActorRequiredError extends Error {
|
|
13
|
+
readonly code = "SAAS_ACTOR_REQUIRED";
|
|
14
|
+
readonly operation: SaasOperation;
|
|
15
|
+
readonly organizationId: string;
|
|
16
|
+
readonly workspaceId?: string;
|
|
17
|
+
constructor(context: SaasPermissionContext);
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export class SaasPermissionError extends Error {
|
|
2
|
+
code = 'SAAS_PERMISSION_DENIED';
|
|
3
|
+
operation;
|
|
4
|
+
organizationId;
|
|
5
|
+
workspaceId;
|
|
6
|
+
actorUserId;
|
|
7
|
+
requiredRole;
|
|
8
|
+
actorRole;
|
|
9
|
+
constructor(context, requiredRole, actorRole) {
|
|
10
|
+
const actor = context.actorUserId ?? 'unknown-actor';
|
|
11
|
+
const workspaceSuffix = context.workspaceId ? ` workspace='${context.workspaceId}'` : '';
|
|
12
|
+
const actualRole = actorRole ?? 'none';
|
|
13
|
+
super(`Permission denied for operation '${context.operation}'. actor='${actor}' organization='${context.organizationId}'${workspaceSuffix} requiredRole='${requiredRole}' actualRole='${actualRole}'.`);
|
|
14
|
+
this.name = 'SaasPermissionError';
|
|
15
|
+
this.operation = context.operation;
|
|
16
|
+
this.organizationId = context.organizationId;
|
|
17
|
+
this.workspaceId = context.workspaceId;
|
|
18
|
+
this.actorUserId = context.actorUserId;
|
|
19
|
+
this.requiredRole = requiredRole;
|
|
20
|
+
this.actorRole = actorRole;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export class SaasActorRequiredError extends Error {
|
|
24
|
+
code = 'SAAS_ACTOR_REQUIRED';
|
|
25
|
+
operation;
|
|
26
|
+
organizationId;
|
|
27
|
+
workspaceId;
|
|
28
|
+
constructor(context) {
|
|
29
|
+
const workspaceSuffix = context.workspaceId ? ` workspace='${context.workspaceId}'` : '';
|
|
30
|
+
super(`Actor is required for operation '${context.operation}'. organization='${context.organizationId}'${workspaceSuffix}.`);
|
|
31
|
+
this.name = 'SaasActorRequiredError';
|
|
32
|
+
this.operation = context.operation;
|
|
33
|
+
this.organizationId = context.organizationId;
|
|
34
|
+
this.workspaceId = context.workspaceId;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { IngestOptions, SaasPlan, SaasPolicy, SaasPolicyInput, SaasPolicyOverrides, SaasQueryOptions, SaasRepo, SaasRole, SaasSnapshot, SaasStore, SaasWorkspace, ScopedIdentity } from './types.js';
|
|
2
|
+
import { DEFAULT_ORGANIZATION_ID } from './constants.js';
|
|
3
|
+
export declare function resolveSaasPolicy(policy?: SaasPolicyInput): SaasPolicy;
|
|
4
|
+
export declare function normalizePlan(plan?: string): SaasPlan;
|
|
5
|
+
export declare function normalizeRole(role?: string): SaasRole;
|
|
6
|
+
export declare function hasRoleAtLeast(role: SaasRole | undefined, requiredRole: SaasRole): boolean;
|
|
7
|
+
export declare function workspaceKey(organizationId: string, workspaceId: string): string;
|
|
8
|
+
export declare function membershipKey(organizationId: string, workspaceId: string, userId: string): string;
|
|
9
|
+
export declare function monthKey(isoDate: string): string;
|
|
10
|
+
export declare function resolveScopedIdentity(options: IngestOptions): ScopedIdentity;
|
|
11
|
+
export declare function isWorkspaceActive(workspace: SaasWorkspace): boolean;
|
|
12
|
+
export declare function isRepoActive(repo: SaasRepo): boolean;
|
|
13
|
+
export declare function matchesTenantScope(snapshot: SaasSnapshot, options?: SaasQueryOptions): boolean;
|
|
14
|
+
export declare function matchesWorkspaceScope(workspace: SaasWorkspace, options?: SaasQueryOptions): boolean;
|
|
15
|
+
export declare function matchesRepoScope(repo: SaasRepo, options?: SaasQueryOptions): boolean;
|
|
16
|
+
export declare function computeRunsPerMonth(snapshots: SaasSnapshot[]): Record<string, number>;
|
|
17
|
+
export declare function computeUsersRegistered(store: SaasStore, snapshots: SaasSnapshot[], options?: SaasQueryOptions): number;
|
|
18
|
+
export declare function escapeHtml(value: string): string;
|
|
19
|
+
export declare function mergePolicy(policy: SaasPolicyOverrides | undefined, base: SaasStore['policy']): SaasPolicy;
|
|
20
|
+
export { DEFAULT_ORGANIZATION_ID };
|
|
21
|
+
//# sourceMappingURL=helpers.d.ts.map
|