@salesforce/b2c-tooling-sdk 1.0.1 → 1.2.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/dist/cjs/cli/base-command.d.ts +33 -8
- package/dist/cjs/cli/base-command.js +92 -35
- package/dist/cjs/cli/base-command.js.map +1 -1
- package/dist/cjs/clients/middleware.d.ts +15 -10
- package/dist/cjs/clients/middleware.js +22 -15
- package/dist/cjs/clients/middleware.js.map +1 -1
- package/dist/cjs/clients/webdav.d.ts +22 -0
- package/dist/cjs/clients/webdav.js +46 -0
- package/dist/cjs/clients/webdav.js.map +1 -1
- package/dist/cjs/config/dw-json.d.ts +32 -0
- package/dist/cjs/config/dw-json.js.map +1 -1
- package/dist/cjs/config/mapping.js +55 -0
- package/dist/cjs/config/mapping.js.map +1 -1
- package/dist/cjs/config/sources/env-source.js +3 -1
- package/dist/cjs/config/sources/env-source.js.map +1 -1
- package/dist/cjs/config/types.d.ts +15 -0
- package/dist/cjs/index.d.ts +3 -2
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/operations/code/watch.js +98 -75
- package/dist/cjs/operations/code/watch.js.map +1 -1
- package/dist/cjs/operations/debug/dap-adapter.d.ts +72 -0
- package/dist/cjs/operations/debug/dap-adapter.js +505 -0
- package/dist/cjs/operations/debug/dap-adapter.js.map +1 -0
- package/dist/cjs/operations/debug/debug-session.d.ts +51 -0
- package/dist/cjs/operations/debug/debug-session.js +219 -0
- package/dist/cjs/operations/debug/debug-session.js.map +1 -0
- package/dist/cjs/operations/debug/index.d.ts +17 -0
- package/dist/cjs/operations/debug/index.js +19 -0
- package/dist/cjs/operations/debug/index.js.map +1 -0
- package/dist/cjs/operations/debug/sdapi-client.d.ts +44 -0
- package/dist/cjs/operations/debug/sdapi-client.js +169 -0
- package/dist/cjs/operations/debug/sdapi-client.js.map +1 -0
- package/dist/cjs/operations/debug/source-mapping.d.ts +13 -0
- package/dist/cjs/operations/debug/source-mapping.js +57 -0
- package/dist/cjs/operations/debug/source-mapping.js.map +1 -0
- package/dist/cjs/operations/debug/types.d.ts +95 -0
- package/dist/cjs/operations/debug/types.js +2 -0
- package/dist/cjs/operations/debug/types.js.map +1 -0
- package/dist/cjs/operations/debug/variable-store.d.ts +35 -0
- package/dist/cjs/operations/debug/variable-store.js +52 -0
- package/dist/cjs/operations/debug/variable-store.js.map +1 -0
- package/dist/cjs/safety/index.d.ts +7 -2
- package/dist/cjs/safety/index.js +5 -1
- package/dist/cjs/safety/index.js.map +1 -1
- package/dist/cjs/safety/safety-guard.d.ts +92 -0
- package/dist/cjs/safety/safety-guard.js +270 -0
- package/dist/cjs/safety/safety-guard.js.map +1 -0
- package/dist/cjs/safety/safety-middleware.d.ts +86 -1
- package/dist/cjs/safety/safety-middleware.js +165 -16
- package/dist/cjs/safety/safety-middleware.js.map +1 -1
- package/dist/cjs/safety/types.d.ts +81 -0
- package/dist/cjs/safety/types.js +16 -0
- package/dist/cjs/safety/types.js.map +1 -0
- package/dist/cjs/safety/with-confirmation.d.ts +58 -0
- package/dist/cjs/safety/with-confirmation.js +67 -0
- package/dist/cjs/safety/with-confirmation.js.map +1 -0
- package/dist/cjs/ux/confirm.d.ts +14 -0
- package/dist/cjs/ux/confirm.js +36 -0
- package/dist/cjs/ux/confirm.js.map +1 -0
- package/dist/esm/cli/base-command.d.ts +33 -8
- package/dist/esm/cli/base-command.js +92 -35
- package/dist/esm/cli/base-command.js.map +1 -1
- package/dist/esm/clients/middleware.d.ts +15 -10
- package/dist/esm/clients/middleware.js +22 -15
- package/dist/esm/clients/middleware.js.map +1 -1
- package/dist/esm/clients/webdav.d.ts +22 -0
- package/dist/esm/clients/webdav.js +46 -0
- package/dist/esm/clients/webdav.js.map +1 -1
- package/dist/esm/config/dw-json.d.ts +32 -0
- package/dist/esm/config/dw-json.js.map +1 -1
- package/dist/esm/config/mapping.js +55 -0
- package/dist/esm/config/mapping.js.map +1 -1
- package/dist/esm/config/sources/env-source.js +3 -1
- package/dist/esm/config/sources/env-source.js.map +1 -1
- package/dist/esm/config/types.d.ts +15 -0
- package/dist/esm/index.d.ts +3 -2
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/operations/code/watch.js +98 -75
- package/dist/esm/operations/code/watch.js.map +1 -1
- package/dist/esm/operations/debug/dap-adapter.d.ts +72 -0
- package/dist/esm/operations/debug/dap-adapter.js +505 -0
- package/dist/esm/operations/debug/dap-adapter.js.map +1 -0
- package/dist/esm/operations/debug/debug-session.d.ts +51 -0
- package/dist/esm/operations/debug/debug-session.js +219 -0
- package/dist/esm/operations/debug/debug-session.js.map +1 -0
- package/dist/esm/operations/debug/index.d.ts +17 -0
- package/dist/esm/operations/debug/index.js +19 -0
- package/dist/esm/operations/debug/index.js.map +1 -0
- package/dist/esm/operations/debug/sdapi-client.d.ts +44 -0
- package/dist/esm/operations/debug/sdapi-client.js +169 -0
- package/dist/esm/operations/debug/sdapi-client.js.map +1 -0
- package/dist/esm/operations/debug/source-mapping.d.ts +13 -0
- package/dist/esm/operations/debug/source-mapping.js +57 -0
- package/dist/esm/operations/debug/source-mapping.js.map +1 -0
- package/dist/esm/operations/debug/types.d.ts +95 -0
- package/dist/esm/operations/debug/types.js +2 -0
- package/dist/esm/operations/debug/types.js.map +1 -0
- package/dist/esm/operations/debug/variable-store.d.ts +35 -0
- package/dist/esm/operations/debug/variable-store.js +52 -0
- package/dist/esm/operations/debug/variable-store.js.map +1 -0
- package/dist/esm/safety/index.d.ts +7 -2
- package/dist/esm/safety/index.js +5 -1
- package/dist/esm/safety/index.js.map +1 -1
- package/dist/esm/safety/safety-guard.d.ts +92 -0
- package/dist/esm/safety/safety-guard.js +270 -0
- package/dist/esm/safety/safety-guard.js.map +1 -0
- package/dist/esm/safety/safety-middleware.d.ts +86 -1
- package/dist/esm/safety/safety-middleware.js +165 -16
- package/dist/esm/safety/safety-middleware.js.map +1 -1
- package/dist/esm/safety/types.d.ts +81 -0
- package/dist/esm/safety/types.js +16 -0
- package/dist/esm/safety/types.js.map +1 -0
- package/dist/esm/safety/with-confirmation.d.ts +58 -0
- package/dist/esm/safety/with-confirmation.js +67 -0
- package/dist/esm/safety/with-confirmation.js.map +1 -0
- package/dist/esm/ux/confirm.d.ts +14 -0
- package/dist/esm/ux/confirm.js +36 -0
- package/dist/esm/ux/confirm.js.map +1 -0
- package/package.json +14 -3
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { SafetyRule } from './types.js';
|
|
2
|
+
import type { SafetyEvaluation } from './types.js';
|
|
1
3
|
/**
|
|
2
4
|
* Safety levels for preventing destructive operations.
|
|
3
5
|
*
|
|
@@ -7,11 +9,34 @@
|
|
|
7
9
|
* - READ_ONLY: Block all write operations (only GET allowed)
|
|
8
10
|
*/
|
|
9
11
|
export type SafetyLevel = 'NONE' | 'NO_DELETE' | 'NO_UPDATE' | 'READ_ONLY';
|
|
12
|
+
/**
|
|
13
|
+
* Safety configuration.
|
|
14
|
+
*
|
|
15
|
+
* Supports both simple level-based blocking and granular per-rule actions.
|
|
16
|
+
*/
|
|
10
17
|
export interface SafetyConfig {
|
|
18
|
+
/** The base safety level. */
|
|
11
19
|
level: SafetyLevel;
|
|
20
|
+
/** When true, operations that the level would block require confirmation instead of hard-blocking. */
|
|
21
|
+
confirm?: boolean;
|
|
22
|
+
/** Ordered list of rules. First matching rule wins. */
|
|
23
|
+
rules?: SafetyRule[];
|
|
12
24
|
}
|
|
13
25
|
/**
|
|
14
|
-
*
|
|
26
|
+
* Returns the more restrictive of two safety levels.
|
|
27
|
+
*/
|
|
28
|
+
export declare function maxSafetyLevel(a: SafetyLevel, b: SafetyLevel): SafetyLevel;
|
|
29
|
+
/**
|
|
30
|
+
* Check if a string is a valid SafetyLevel.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isValidSafetyLevel(value: string): value is SafetyLevel;
|
|
33
|
+
/**
|
|
34
|
+
* Parse a string to a SafetyLevel, returning undefined for invalid values.
|
|
35
|
+
* Accepts case-insensitive input and converts dashes to underscores.
|
|
36
|
+
*/
|
|
37
|
+
export declare function parseSafetyLevelString(value: string | undefined): SafetyLevel | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Safety error thrown when an operation is blocked by safety configuration.
|
|
15
40
|
*/
|
|
16
41
|
export declare class SafetyBlockedError extends Error {
|
|
17
42
|
readonly method: string;
|
|
@@ -19,6 +44,28 @@ export declare class SafetyBlockedError extends Error {
|
|
|
19
44
|
readonly safetyLevel: SafetyLevel;
|
|
20
45
|
constructor(message: string, method: string, url: string, safetyLevel: SafetyLevel);
|
|
21
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Error thrown when an operation requires interactive confirmation.
|
|
49
|
+
*
|
|
50
|
+
* Callers can catch this error, prompt the user, and retry the operation
|
|
51
|
+
* using {@link withSafetyConfirmation}.
|
|
52
|
+
*/
|
|
53
|
+
export declare class SafetyConfirmationRequired extends Error {
|
|
54
|
+
readonly evaluation: SafetyEvaluation;
|
|
55
|
+
constructor(evaluation: SafetyEvaluation);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Checks if an HTTP operation should be blocked based on a safety level.
|
|
59
|
+
*
|
|
60
|
+
* This is the low-level level check. For full rule-based evaluation,
|
|
61
|
+
* use {@link SafetyGuard.evaluate}.
|
|
62
|
+
*
|
|
63
|
+
* @param method - HTTP method (GET, POST, PUT, PATCH, DELETE)
|
|
64
|
+
* @param path - URL pathname
|
|
65
|
+
* @param level - Safety level to check against
|
|
66
|
+
* @returns Error message if blocked, undefined if allowed
|
|
67
|
+
*/
|
|
68
|
+
export declare function checkLevelViolation(method: string, path: string, level: SafetyLevel): string | undefined;
|
|
22
69
|
/**
|
|
23
70
|
* Checks if an HTTP operation should be blocked based on safety configuration.
|
|
24
71
|
*
|
|
@@ -26,6 +73,7 @@ export declare class SafetyBlockedError extends Error {
|
|
|
26
73
|
* @param url - Request URL
|
|
27
74
|
* @param config - Safety configuration
|
|
28
75
|
* @returns Error message if blocked, undefined if allowed
|
|
76
|
+
* @deprecated Use {@link SafetyGuard.evaluate} for full rule-based evaluation.
|
|
29
77
|
*/
|
|
30
78
|
export declare function checkSafetyViolation(method: string, url: string, config: SafetyConfig): string | undefined;
|
|
31
79
|
/**
|
|
@@ -43,3 +91,40 @@ export declare function getSafetyLevel(defaultLevel?: SafetyLevel): SafetyLevel;
|
|
|
43
91
|
* Get a user-friendly description of the safety level.
|
|
44
92
|
*/
|
|
45
93
|
export declare function describeSafetyLevel(level: SafetyLevel): string;
|
|
94
|
+
/** Validated safety config fragment (shared by global and per-instance). */
|
|
95
|
+
export interface SafetyConfigFragment {
|
|
96
|
+
level?: SafetyLevel;
|
|
97
|
+
confirm?: boolean;
|
|
98
|
+
rules?: SafetyRule[];
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Load global safety configuration from a JSON file.
|
|
102
|
+
*
|
|
103
|
+
* Resolution order:
|
|
104
|
+
* 1. `SFCC_SAFETY_CONFIG` env var — explicit path to a safety config file
|
|
105
|
+
* 2. `{configDir}/safety.json` — oclif config directory (e.g., `~/.config/b2c/safety.json`)
|
|
106
|
+
*
|
|
107
|
+
* The file has the same shape as the `safety` object in dw.json:
|
|
108
|
+
* ```json
|
|
109
|
+
* { "level": "NO_DELETE", "confirm": true, "rules": [...] }
|
|
110
|
+
* ```
|
|
111
|
+
*
|
|
112
|
+
* @param configDir - oclif config directory path (e.g., `this.config.configDir`)
|
|
113
|
+
* @returns Validated safety config fragment, or undefined if no file found
|
|
114
|
+
*/
|
|
115
|
+
export declare function loadGlobalSafetyConfig(configDir?: string): SafetyConfigFragment | undefined;
|
|
116
|
+
/**
|
|
117
|
+
* Compute effective safety config by merging environment variables, global
|
|
118
|
+
* safety config, and per-instance config.
|
|
119
|
+
*
|
|
120
|
+
* Merge strategy:
|
|
121
|
+
* - **Level**: `max(env, global, instance)` — most restrictive wins
|
|
122
|
+
* - **Confirm**: OR across all sources
|
|
123
|
+
* - **Rules**: instance rules first, then global rules (first-match-wins,
|
|
124
|
+
* so instance rules can override global policy)
|
|
125
|
+
*
|
|
126
|
+
* @param instanceSafety - Per-instance safety config from dw.json
|
|
127
|
+
* @param globalSafety - Global safety config from safety.json
|
|
128
|
+
* @returns Merged SafetyConfig
|
|
129
|
+
*/
|
|
130
|
+
export declare function resolveEffectiveSafetyConfig(instanceSafety?: SafetyConfigFragment, globalSafety?: SafetyConfigFragment): SafetyConfig;
|
|
@@ -3,9 +3,47 @@
|
|
|
3
3
|
* SPDX-License-Identifier: Apache-2
|
|
4
4
|
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
|
|
5
5
|
*/
|
|
6
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
6
8
|
import { getLogger } from '../logging/logger.js';
|
|
9
|
+
import { isValidSafetyAction } from './types.js';
|
|
7
10
|
/**
|
|
8
|
-
*
|
|
11
|
+
* Ordering of safety levels from least to most restrictive.
|
|
12
|
+
*/
|
|
13
|
+
const LEVEL_ORDER = {
|
|
14
|
+
NONE: 0,
|
|
15
|
+
NO_DELETE: 1,
|
|
16
|
+
NO_UPDATE: 2,
|
|
17
|
+
READ_ONLY: 3,
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Valid safety level strings.
|
|
21
|
+
*/
|
|
22
|
+
const VALID_LEVELS = ['NONE', 'NO_DELETE', 'NO_UPDATE', 'READ_ONLY'];
|
|
23
|
+
/**
|
|
24
|
+
* Returns the more restrictive of two safety levels.
|
|
25
|
+
*/
|
|
26
|
+
export function maxSafetyLevel(a, b) {
|
|
27
|
+
return LEVEL_ORDER[a] >= LEVEL_ORDER[b] ? a : b;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Check if a string is a valid SafetyLevel.
|
|
31
|
+
*/
|
|
32
|
+
export function isValidSafetyLevel(value) {
|
|
33
|
+
return VALID_LEVELS.includes(value);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Parse a string to a SafetyLevel, returning undefined for invalid values.
|
|
37
|
+
* Accepts case-insensitive input and converts dashes to underscores.
|
|
38
|
+
*/
|
|
39
|
+
export function parseSafetyLevelString(value) {
|
|
40
|
+
if (!value)
|
|
41
|
+
return undefined;
|
|
42
|
+
const normalized = value.toUpperCase().replace(/-/g, '_');
|
|
43
|
+
return isValidSafetyLevel(normalized) ? normalized : undefined;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Safety error thrown when an operation is blocked by safety configuration.
|
|
9
47
|
*/
|
|
10
48
|
export class SafetyBlockedError extends Error {
|
|
11
49
|
method;
|
|
@@ -20,35 +58,50 @@ export class SafetyBlockedError extends Error {
|
|
|
20
58
|
}
|
|
21
59
|
}
|
|
22
60
|
/**
|
|
23
|
-
*
|
|
61
|
+
* Error thrown when an operation requires interactive confirmation.
|
|
62
|
+
*
|
|
63
|
+
* Callers can catch this error, prompt the user, and retry the operation
|
|
64
|
+
* using {@link withSafetyConfirmation}.
|
|
65
|
+
*/
|
|
66
|
+
export class SafetyConfirmationRequired extends Error {
|
|
67
|
+
evaluation;
|
|
68
|
+
constructor(evaluation) {
|
|
69
|
+
super(`Confirmation required: ${evaluation.reason}`);
|
|
70
|
+
this.evaluation = evaluation;
|
|
71
|
+
this.name = 'SafetyConfirmationRequired';
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Checks if an HTTP operation should be blocked based on a safety level.
|
|
76
|
+
*
|
|
77
|
+
* This is the low-level level check. For full rule-based evaluation,
|
|
78
|
+
* use {@link SafetyGuard.evaluate}.
|
|
24
79
|
*
|
|
25
80
|
* @param method - HTTP method (GET, POST, PUT, PATCH, DELETE)
|
|
26
|
-
* @param
|
|
27
|
-
* @param
|
|
81
|
+
* @param path - URL pathname
|
|
82
|
+
* @param level - Safety level to check against
|
|
28
83
|
* @returns Error message if blocked, undefined if allowed
|
|
29
84
|
*/
|
|
30
|
-
export function
|
|
85
|
+
export function checkLevelViolation(method, path, level) {
|
|
31
86
|
const upperMethod = method.toUpperCase();
|
|
32
|
-
|
|
33
|
-
switch (config.level) {
|
|
87
|
+
switch (level) {
|
|
34
88
|
case 'NONE':
|
|
35
|
-
return undefined;
|
|
89
|
+
return undefined;
|
|
36
90
|
case 'NO_DELETE':
|
|
37
91
|
if (upperMethod === 'DELETE') {
|
|
38
92
|
return `Delete operation blocked: DELETE ${path} (NO_DELETE mode prevents deletions)`;
|
|
39
93
|
}
|
|
40
94
|
return undefined;
|
|
41
|
-
case 'NO_UPDATE':
|
|
42
|
-
// Block DELETE operations
|
|
95
|
+
case 'NO_UPDATE': {
|
|
43
96
|
if (upperMethod === 'DELETE') {
|
|
44
97
|
return `Delete operation blocked: DELETE ${path} (NO_UPDATE mode prevents deletions)`;
|
|
45
98
|
}
|
|
46
|
-
// Block operations that contain reset, stop, restart in path or might be destructive
|
|
47
99
|
const destructivePatterns = ['/reset', '/stop', '/restart', '/operations'];
|
|
48
100
|
if (destructivePatterns.some((pattern) => path.includes(pattern)) && upperMethod === 'POST') {
|
|
49
101
|
return `Destructive operation blocked: POST ${path} (NO_UPDATE mode prevents reset/stop/restart)`;
|
|
50
102
|
}
|
|
51
103
|
return undefined;
|
|
104
|
+
}
|
|
52
105
|
case 'READ_ONLY':
|
|
53
106
|
if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(upperMethod)) {
|
|
54
107
|
return `Write operation blocked: ${upperMethod} ${path} (READ_ONLY mode prevents all modifications)`;
|
|
@@ -58,6 +111,19 @@ export function checkSafetyViolation(method, url, config) {
|
|
|
58
111
|
return undefined;
|
|
59
112
|
}
|
|
60
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Checks if an HTTP operation should be blocked based on safety configuration.
|
|
116
|
+
*
|
|
117
|
+
* @param method - HTTP method (GET, POST, PUT, PATCH, DELETE)
|
|
118
|
+
* @param url - Request URL
|
|
119
|
+
* @param config - Safety configuration
|
|
120
|
+
* @returns Error message if blocked, undefined if allowed
|
|
121
|
+
* @deprecated Use {@link SafetyGuard.evaluate} for full rule-based evaluation.
|
|
122
|
+
*/
|
|
123
|
+
export function checkSafetyViolation(method, url, config) {
|
|
124
|
+
const path = new URL(url, 'http://dummy').pathname;
|
|
125
|
+
return checkLevelViolation(method, path, config.level);
|
|
126
|
+
}
|
|
61
127
|
/**
|
|
62
128
|
* Parse safety level from environment variable.
|
|
63
129
|
*
|
|
@@ -71,11 +137,10 @@ export function checkSafetyViolation(method, url, config) {
|
|
|
71
137
|
export function getSafetyLevel(defaultLevel = 'NONE') {
|
|
72
138
|
const safetyLevelEnv = process.env['SFCC_SAFETY_LEVEL'];
|
|
73
139
|
if (safetyLevelEnv) {
|
|
74
|
-
const
|
|
75
|
-
if (
|
|
76
|
-
return
|
|
77
|
-
}
|
|
78
|
-
getLogger().warn({ envValue: safetyLevelEnv, validValues: ['NONE', 'NO_DELETE', 'NO_UPDATE', 'READ_ONLY'] }, 'SFCC_SAFETY_LEVEL has an invalid value; using default safety level');
|
|
140
|
+
const parsed = parseSafetyLevelString(safetyLevelEnv);
|
|
141
|
+
if (parsed)
|
|
142
|
+
return parsed;
|
|
143
|
+
getLogger().warn({ envValue: safetyLevelEnv, validValues: VALID_LEVELS }, 'SFCC_SAFETY_LEVEL has an invalid value; using default safety level');
|
|
79
144
|
}
|
|
80
145
|
return defaultLevel;
|
|
81
146
|
}
|
|
@@ -96,4 +161,88 @@ export function describeSafetyLevel(level) {
|
|
|
96
161
|
return 'Unknown safety level';
|
|
97
162
|
}
|
|
98
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* Load global safety configuration from a JSON file.
|
|
166
|
+
*
|
|
167
|
+
* Resolution order:
|
|
168
|
+
* 1. `SFCC_SAFETY_CONFIG` env var — explicit path to a safety config file
|
|
169
|
+
* 2. `{configDir}/safety.json` — oclif config directory (e.g., `~/.config/b2c/safety.json`)
|
|
170
|
+
*
|
|
171
|
+
* The file has the same shape as the `safety` object in dw.json:
|
|
172
|
+
* ```json
|
|
173
|
+
* { "level": "NO_DELETE", "confirm": true, "rules": [...] }
|
|
174
|
+
* ```
|
|
175
|
+
*
|
|
176
|
+
* @param configDir - oclif config directory path (e.g., `this.config.configDir`)
|
|
177
|
+
* @returns Validated safety config fragment, or undefined if no file found
|
|
178
|
+
*/
|
|
179
|
+
export function loadGlobalSafetyConfig(configDir) {
|
|
180
|
+
const logger = getLogger();
|
|
181
|
+
// 1. Check SFCC_SAFETY_CONFIG env var
|
|
182
|
+
const envPath = process.env['SFCC_SAFETY_CONFIG'];
|
|
183
|
+
const filePath = envPath || (configDir ? join(configDir, 'safety.json') : undefined);
|
|
184
|
+
if (!filePath || !existsSync(filePath)) {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
const raw = JSON.parse(readFileSync(filePath, 'utf-8'));
|
|
189
|
+
const result = {};
|
|
190
|
+
if (raw.level) {
|
|
191
|
+
const parsed = parseSafetyLevelString(raw.level);
|
|
192
|
+
if (parsed) {
|
|
193
|
+
result.level = parsed;
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
logger.warn({ level: raw.level, file: filePath }, 'Invalid safety level in global safety config; ignoring');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (raw.confirm !== undefined) {
|
|
200
|
+
result.confirm = raw.confirm === true;
|
|
201
|
+
}
|
|
202
|
+
if (raw.rules && Array.isArray(raw.rules)) {
|
|
203
|
+
result.rules = raw.rules.filter((r) => {
|
|
204
|
+
if (!isValidSafetyAction(r.action)) {
|
|
205
|
+
logger.warn({ rule: r, file: filePath }, 'Invalid safety rule action in global safety config; skipping rule');
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
return true;
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
logger.trace({ filePath, level: result.level, ruleCount: result.rules?.length }, 'Loaded global safety config');
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
logger.warn({ error, file: filePath }, 'Failed to load global safety config; ignoring');
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Compute effective safety config by merging environment variables, global
|
|
221
|
+
* safety config, and per-instance config.
|
|
222
|
+
*
|
|
223
|
+
* Merge strategy:
|
|
224
|
+
* - **Level**: `max(env, global, instance)` — most restrictive wins
|
|
225
|
+
* - **Confirm**: OR across all sources
|
|
226
|
+
* - **Rules**: instance rules first, then global rules (first-match-wins,
|
|
227
|
+
* so instance rules can override global policy)
|
|
228
|
+
*
|
|
229
|
+
* @param instanceSafety - Per-instance safety config from dw.json
|
|
230
|
+
* @param globalSafety - Global safety config from safety.json
|
|
231
|
+
* @returns Merged SafetyConfig
|
|
232
|
+
*/
|
|
233
|
+
export function resolveEffectiveSafetyConfig(instanceSafety, globalSafety) {
|
|
234
|
+
const envLevel = getSafetyLevel('NONE');
|
|
235
|
+
const envConfirm = process.env['SFCC_SAFETY_CONFIRM'] === 'true' || process.env['SFCC_SAFETY_CONFIRM'] === '1';
|
|
236
|
+
const instanceLevel = instanceSafety?.level ?? 'NONE';
|
|
237
|
+
const globalLevel = globalSafety?.level ?? 'NONE';
|
|
238
|
+
// Merge rules: instance first, then global (first-match-wins)
|
|
239
|
+
const instanceRules = instanceSafety?.rules ?? [];
|
|
240
|
+
const globalRules = globalSafety?.rules ?? [];
|
|
241
|
+
const mergedRules = [...instanceRules, ...globalRules];
|
|
242
|
+
return {
|
|
243
|
+
level: maxSafetyLevel(envLevel, maxSafetyLevel(globalLevel, instanceLevel)),
|
|
244
|
+
confirm: envConfirm || instanceSafety?.confirm === true || globalSafety?.confirm === true,
|
|
245
|
+
rules: mergedRules.length > 0 ? mergedRules : undefined,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
99
248
|
//# sourceMappingURL=safety-middleware.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"safety-middleware.js","sourceRoot":"","sources":["../../../src/safety/safety-middleware.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAC,SAAS,EAAC,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"safety-middleware.js","sourceRoot":"","sources":["../../../src/safety/safety-middleware.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAC,UAAU,EAAE,YAAY,EAAC,MAAM,SAAS,CAAC;AACjD,OAAO,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AAC/B,OAAO,EAAC,SAAS,EAAC,MAAM,sBAAsB,CAAC;AAE/C,OAAO,EAAC,mBAAmB,EAAC,MAAM,YAAY,CAAC;AA2B/C;;GAEG;AACH,MAAM,WAAW,GAAgC;IAC/C,IAAI,EAAE,CAAC;IACP,SAAS,EAAE,CAAC;IACZ,SAAS,EAAE,CAAC;IACZ,SAAS,EAAE,CAAC;CACb,CAAC;AAEF;;GAEG;AACH,MAAM,YAAY,GAA2B,CAAC,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,CAAU,CAAC;AAEtG;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,CAAc,EAAE,CAAc;IAC3D,OAAO,WAAW,CAAC,CAAC,CAAC,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,OAAO,YAAY,CAAC,QAAQ,CAAC,KAAoB,CAAC,CAAC;AACrD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAyB;IAC9D,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,UAAU,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC1D,OAAO,kBAAkB,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;AACjE,CAAC;AAED;;GAEG;AACH,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAGzB;IACA;IACA;IAJlB,YACE,OAAe,EACC,MAAc,EACd,GAAW,EACX,WAAwB;QAExC,KAAK,CAAC,OAAO,CAAC,CAAC;QAJC,WAAM,GAAN,MAAM,CAAQ;QACd,QAAG,GAAH,GAAG,CAAQ;QACX,gBAAW,GAAX,WAAW,CAAa;QAGxC,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,OAAO,0BAA2B,SAAQ,KAAK;IACvB;IAA5B,YAA4B,UAA4B;QACtD,KAAK,CAAC,0BAA0B,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;QAD3B,eAAU,GAAV,UAAU,CAAkB;QAEtD,IAAI,CAAC,IAAI,GAAG,4BAA4B,CAAC;IAC3C,CAAC;CACF;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAc,EAAE,IAAY,EAAE,KAAkB;IAClF,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;IAEzC,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,MAAM;YACT,OAAO,SAAS,CAAC;QAEnB,KAAK,WAAW;YACd,IAAI,WAAW,KAAK,QAAQ,EAAE,CAAC;gBAC7B,OAAO,oCAAoC,IAAI,sCAAsC,CAAC;YACxF,CAAC;YACD,OAAO,SAAS,CAAC;QAEnB,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,IAAI,WAAW,KAAK,QAAQ,EAAE,CAAC;gBAC7B,OAAO,oCAAoC,IAAI,sCAAsC,CAAC;YACxF,CAAC;YACD,MAAM,mBAAmB,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,aAAa,CAAC,CAAC;YAC3E,IAAI,mBAAmB,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,WAAW,KAAK,MAAM,EAAE,CAAC;gBAC5F,OAAO,uCAAuC,IAAI,+CAA+C,CAAC;YACpG,CAAC;YACD,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,KAAK,WAAW;YACd,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;gBAC7D,OAAO,4BAA4B,WAAW,IAAI,IAAI,8CAA8C,CAAC;YACvG,CAAC;YACD,OAAO,SAAS,CAAC;QAEnB;YACE,OAAO,SAAS,CAAC;IACrB,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAc,EAAE,GAAW,EAAE,MAAoB;IACpF,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,QAAQ,CAAC;IACnD,OAAO,mBAAmB,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;AACzD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAAC,eAA4B,MAAM;IAC/D,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;IACxD,IAAI,cAAc,EAAE,CAAC;QACnB,MAAM,MAAM,GAAG,sBAAsB,CAAC,cAAc,CAAC,CAAC;QACtD,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC;QAE1B,SAAS,EAAE,CAAC,IAAI,CACd,EAAC,QAAQ,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAC,EACrD,oEAAoE,CACrE,CAAC;IACJ,CAAC;IAED,OAAO,YAAY,CAAC;AACtB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAkB;IACpD,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,MAAM;YACT,OAAO,wBAAwB,CAAC;QAClC,KAAK,WAAW;YACd,OAAO,2BAA2B,CAAC;QACrC,KAAK,WAAW;YACd,OAAO,+DAA+D,CAAC;QACzE,KAAK,WAAW;YACd,OAAO,+CAA+C,CAAC;QACzD;YACE,OAAO,sBAAsB,CAAC;IAClC,CAAC;AACH,CAAC;AAgBD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,sBAAsB,CAAC,SAAkB;IACvD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAE3B,sCAAsC;IACtC,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;IAClD,MAAM,QAAQ,GAAG,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAErF,IAAI,CAAC,QAAQ,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACvC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAoB,CAAC;QAC3E,MAAM,MAAM,GAAyB,EAAE,CAAC;QAExC,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;YACd,MAAM,MAAM,GAAG,sBAAsB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACjD,IAAI,MAAM,EAAE,CAAC;gBACX,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC;YACxB,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,IAAI,CAAC,EAAC,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAC,EAAE,wDAAwD,CAAC,CAAC;YAC5G,CAAC;QACH,CAAC;QAED,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YAC9B,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC,OAAO,KAAK,IAAI,CAAC;QACxC,CAAC;QAED,IAAI,GAAG,CAAC,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1C,MAAM,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;gBACpC,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;oBACnC,MAAM,CAAC,IAAI,CAAC,EAAC,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAC,EAAE,mEAAmE,CAAC,CAAC;oBAC5G,OAAO,KAAK,CAAC;gBACf,CAAC;gBACD,OAAO,IAAI,CAAC;YACd,CAAC,CAAiB,CAAC;QACrB,CAAC;QAED,MAAM,CAAC,KAAK,CAAC,EAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAC,EAAE,6BAA6B,CAAC,CAAC;QAC9G,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,IAAI,CAAC,EAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAC,EAAE,+CAA+C,CAAC,CAAC;QACtF,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,4BAA4B,CAC1C,cAAqC,EACrC,YAAmC;IAEnC,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,KAAK,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,KAAK,GAAG,CAAC;IAE/G,MAAM,aAAa,GAAG,cAAc,EAAE,KAAK,IAAI,MAAM,CAAC;IACtD,MAAM,WAAW,GAAG,YAAY,EAAE,KAAK,IAAI,MAAM,CAAC;IAElD,8DAA8D;IAC9D,MAAM,aAAa,GAAG,cAAc,EAAE,KAAK,IAAI,EAAE,CAAC;IAClD,MAAM,WAAW,GAAG,YAAY,EAAE,KAAK,IAAI,EAAE,CAAC;IAC9C,MAAM,WAAW,GAAG,CAAC,GAAG,aAAa,EAAE,GAAG,WAAW,CAAC,CAAC;IAEvD,OAAO;QACL,KAAK,EAAE,cAAc,CAAC,QAAQ,EAAE,cAAc,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;QAC3E,OAAO,EAAE,UAAU,IAAI,cAAc,EAAE,OAAO,KAAK,IAAI,IAAI,YAAY,EAAE,OAAO,KAAK,IAAI;QACzF,KAAK,EAAE,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS;KACxD,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the safety evaluation system.
|
|
3
|
+
*
|
|
4
|
+
* @module safety/types
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Action that a safety evaluation can produce.
|
|
8
|
+
*
|
|
9
|
+
* - `allow`: Operation is permitted
|
|
10
|
+
* - `block`: Operation is refused
|
|
11
|
+
* - `confirm`: Operation requires interactive confirmation before proceeding
|
|
12
|
+
*/
|
|
13
|
+
export type SafetyAction = 'allow' | 'block' | 'confirm';
|
|
14
|
+
/**
|
|
15
|
+
* A safety rule that matches operations and specifies an action.
|
|
16
|
+
*
|
|
17
|
+
* Rules support three matcher types, all using glob patterns (via minimatch):
|
|
18
|
+
* - `method` + `path`: Matches HTTP requests by method and URL path
|
|
19
|
+
* - `job`: Matches job execution by job ID (extracted from OCAPI URLs)
|
|
20
|
+
* - `command`: Matches CLI commands by oclif command ID (e.g., "sandbox:delete")
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```json
|
|
24
|
+
* { "job": "sfcc-site-archive-export", "action": "allow" }
|
|
25
|
+
* { "command": "ecdn:cache:purge", "action": "confirm" }
|
|
26
|
+
* { "method": "DELETE", "path": "/code_versions/*", "action": "block" }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export interface SafetyRule {
|
|
30
|
+
/** HTTP method pattern (e.g., "POST", "DELETE"). Omit to match any method. */
|
|
31
|
+
method?: string;
|
|
32
|
+
/** URL path glob pattern (e.g., "/jobs/*/executions"). Matched with minimatch. */
|
|
33
|
+
path?: string;
|
|
34
|
+
/** Job ID glob pattern. Matches OCAPI job execution URLs by job ID. */
|
|
35
|
+
job?: string;
|
|
36
|
+
/** CLI command ID glob pattern (e.g., "sandbox:*", "ecdn:cache:purge"). */
|
|
37
|
+
command?: string;
|
|
38
|
+
/** Action to take when this rule matches. */
|
|
39
|
+
action: SafetyAction;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* An operation being evaluated for safety.
|
|
43
|
+
*
|
|
44
|
+
* Constructed by the caller (HTTP middleware, CLI command, etc.) and passed
|
|
45
|
+
* to {@link SafetyGuard.evaluate} for rule matching.
|
|
46
|
+
*/
|
|
47
|
+
export interface SafetyOperation {
|
|
48
|
+
/** The type of operation being evaluated. */
|
|
49
|
+
type: 'http' | 'job' | 'command';
|
|
50
|
+
/** HTTP method (for http operations). */
|
|
51
|
+
method?: string;
|
|
52
|
+
/** Full request URL (for http operations). */
|
|
53
|
+
url?: string;
|
|
54
|
+
/** Parsed URL pathname (for http operations). */
|
|
55
|
+
path?: string;
|
|
56
|
+
/** Job identifier (for job operations, or extracted from http operation URLs). */
|
|
57
|
+
jobId?: string;
|
|
58
|
+
/** CLI command ID, e.g., "sandbox:delete" (for command operations). */
|
|
59
|
+
commandId?: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Result of evaluating an operation against safety rules.
|
|
63
|
+
*/
|
|
64
|
+
export interface SafetyEvaluation {
|
|
65
|
+
/** The action determined by evaluation. */
|
|
66
|
+
action: SafetyAction;
|
|
67
|
+
/** Human-readable explanation of why this action was chosen. */
|
|
68
|
+
reason: string;
|
|
69
|
+
/** The operation that was evaluated. */
|
|
70
|
+
operation: SafetyOperation;
|
|
71
|
+
/** The rule that matched, or undefined if the level default was used. */
|
|
72
|
+
rule?: SafetyRule;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Validated safety actions for use in rule parsing.
|
|
76
|
+
*/
|
|
77
|
+
export declare const VALID_SAFETY_ACTIONS: readonly SafetyAction[];
|
|
78
|
+
/**
|
|
79
|
+
* Check if a string is a valid SafetyAction.
|
|
80
|
+
*/
|
|
81
|
+
export declare function isValidSafetyAction(value: string): value is SafetyAction;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025, Salesforce, Inc.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2
|
|
4
|
+
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Validated safety actions for use in rule parsing.
|
|
8
|
+
*/
|
|
9
|
+
export const VALID_SAFETY_ACTIONS = ['allow', 'block', 'confirm'];
|
|
10
|
+
/**
|
|
11
|
+
* Check if a string is a valid SafetyAction.
|
|
12
|
+
*/
|
|
13
|
+
export function isValidSafetyAction(value) {
|
|
14
|
+
return VALID_SAFETY_ACTIONS.includes(value);
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/safety/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAgFH;;GAEG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAA4B,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,CAAU,CAAC;AAEpG;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAa;IAC/C,OAAO,oBAAoB,CAAC,QAAQ,CAAC,KAAqB,CAAC,CAAC;AAC9D,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Confirmation flow utility for safety-guarded operations.
|
|
3
|
+
*
|
|
4
|
+
* @module safety/with-confirmation
|
|
5
|
+
*/
|
|
6
|
+
import type { SafetyGuard } from './safety-guard.js';
|
|
7
|
+
import type { SafetyEvaluation } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Handler that prompts a user for confirmation.
|
|
10
|
+
*
|
|
11
|
+
* Implementations vary by context:
|
|
12
|
+
* - CLI: readline-based prompt
|
|
13
|
+
* - VS Code: `vscode.window.showWarningMessage({ modal: true })`
|
|
14
|
+
* - MCP/non-interactive: always returns `false`
|
|
15
|
+
*
|
|
16
|
+
* @param evaluation - The safety evaluation that triggered confirmation
|
|
17
|
+
* @returns true if the user confirmed, false to cancel
|
|
18
|
+
*/
|
|
19
|
+
export type ConfirmHandler = (evaluation: SafetyEvaluation) => Promise<boolean>;
|
|
20
|
+
/**
|
|
21
|
+
* Execute an operation with safety confirmation support.
|
|
22
|
+
*
|
|
23
|
+
* If the operation throws {@link SafetyConfirmationRequired}, the
|
|
24
|
+
* `confirmHandler` is called. If the user confirms, the operation
|
|
25
|
+
* is retried with a temporary exemption. If the user cancels (or
|
|
26
|
+
* the handler returns false), a {@link SafetyBlockedError} is thrown.
|
|
27
|
+
*
|
|
28
|
+
* Non-confirmation errors are re-thrown as-is.
|
|
29
|
+
*
|
|
30
|
+
* @param guard - The SafetyGuard instance
|
|
31
|
+
* @param operation - The operation to execute
|
|
32
|
+
* @param confirmHandler - Context-specific confirmation handler
|
|
33
|
+
* @returns The operation's return value
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* // CLI usage
|
|
38
|
+
* const result = await withSafetyConfirmation(
|
|
39
|
+
* guard,
|
|
40
|
+
* () => instance.ocapi.POST('/jobs/import/executions', ...),
|
|
41
|
+
* async (eval) => {
|
|
42
|
+
* if (!process.stdin.isTTY) return false;
|
|
43
|
+
* return confirm(`Safety: ${eval.reason}. Proceed?`);
|
|
44
|
+
* },
|
|
45
|
+
* );
|
|
46
|
+
*
|
|
47
|
+
* // VS Code usage
|
|
48
|
+
* const result = await withSafetyConfirmation(
|
|
49
|
+
* guard,
|
|
50
|
+
* () => runJobImport(),
|
|
51
|
+
* async (eval) => {
|
|
52
|
+
* const choice = await vscode.window.showWarningMessage(eval.reason, { modal: true }, 'Proceed');
|
|
53
|
+
* return choice === 'Proceed';
|
|
54
|
+
* },
|
|
55
|
+
* );
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export declare function withSafetyConfirmation<T>(guard: SafetyGuard, operation: () => Promise<T>, confirmHandler: ConfirmHandler): Promise<T>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025, Salesforce, Inc.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2
|
|
4
|
+
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { SafetyBlockedError, SafetyConfirmationRequired } from './safety-middleware.js';
|
|
7
|
+
/**
|
|
8
|
+
* Execute an operation with safety confirmation support.
|
|
9
|
+
*
|
|
10
|
+
* If the operation throws {@link SafetyConfirmationRequired}, the
|
|
11
|
+
* `confirmHandler` is called. If the user confirms, the operation
|
|
12
|
+
* is retried with a temporary exemption. If the user cancels (or
|
|
13
|
+
* the handler returns false), a {@link SafetyBlockedError} is thrown.
|
|
14
|
+
*
|
|
15
|
+
* Non-confirmation errors are re-thrown as-is.
|
|
16
|
+
*
|
|
17
|
+
* @param guard - The SafetyGuard instance
|
|
18
|
+
* @param operation - The operation to execute
|
|
19
|
+
* @param confirmHandler - Context-specific confirmation handler
|
|
20
|
+
* @returns The operation's return value
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* // CLI usage
|
|
25
|
+
* const result = await withSafetyConfirmation(
|
|
26
|
+
* guard,
|
|
27
|
+
* () => instance.ocapi.POST('/jobs/import/executions', ...),
|
|
28
|
+
* async (eval) => {
|
|
29
|
+
* if (!process.stdin.isTTY) return false;
|
|
30
|
+
* return confirm(`Safety: ${eval.reason}. Proceed?`);
|
|
31
|
+
* },
|
|
32
|
+
* );
|
|
33
|
+
*
|
|
34
|
+
* // VS Code usage
|
|
35
|
+
* const result = await withSafetyConfirmation(
|
|
36
|
+
* guard,
|
|
37
|
+
* () => runJobImport(),
|
|
38
|
+
* async (eval) => {
|
|
39
|
+
* const choice = await vscode.window.showWarningMessage(eval.reason, { modal: true }, 'Proceed');
|
|
40
|
+
* return choice === 'Proceed';
|
|
41
|
+
* },
|
|
42
|
+
* );
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export async function withSafetyConfirmation(guard, operation, confirmHandler) {
|
|
46
|
+
try {
|
|
47
|
+
return await operation();
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
if (err instanceof SafetyConfirmationRequired) {
|
|
51
|
+
const confirmed = await confirmHandler(err.evaluation);
|
|
52
|
+
if (!confirmed) {
|
|
53
|
+
throw new SafetyBlockedError(`Operation cancelled: ${err.evaluation.reason}`, err.evaluation.operation.method ?? '', err.evaluation.operation.url ?? '', guard.config.level);
|
|
54
|
+
}
|
|
55
|
+
// Temporarily allow this specific operation and retry
|
|
56
|
+
const cleanup = guard.temporarilyAllow(err.evaluation.operation);
|
|
57
|
+
try {
|
|
58
|
+
return await operation();
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
cleanup();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=with-confirmation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"with-confirmation.js","sourceRoot":"","sources":["../../../src/safety/with-confirmation.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAUH,OAAO,EAAC,kBAAkB,EAAE,0BAA0B,EAAC,MAAM,wBAAwB,CAAC;AAetF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,KAAkB,EAClB,SAA2B,EAC3B,cAA8B;IAE9B,IAAI,CAAC;QACH,OAAO,MAAM,SAAS,EAAE,CAAC;IAC3B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,0BAA0B,EAAE,CAAC;YAC9C,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YACvD,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,MAAM,IAAI,kBAAkB,CAC1B,wBAAwB,GAAG,CAAC,UAAU,CAAC,MAAM,EAAE,EAC/C,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,MAAM,IAAI,EAAE,EACrC,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,EAClC,KAAK,CAAC,MAAM,CAAC,KAAK,CACnB,CAAC;YACJ,CAAC;YAED,sDAAsD;YACtD,MAAM,OAAO,GAAG,KAAK,CAAC,gBAAgB,CAAC,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;YACjE,IAAI,CAAC;gBACH,OAAO,MAAM,SAAS,EAAE,CAAC;YAC3B,CAAC;oBAAS,CAAC;gBACT,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ConfirmOptions {
|
|
2
|
+
/** Default to yes when the user presses Enter without typing. Defaults to false (no). */
|
|
3
|
+
defaultYes?: boolean;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Simple yes/no confirmation prompt.
|
|
7
|
+
*
|
|
8
|
+
* Output goes to stderr so it doesn't interfere with structured stdout output.
|
|
9
|
+
*
|
|
10
|
+
* @param message - Prompt message (the hint is appended automatically)
|
|
11
|
+
* @param options - Options to control default behavior
|
|
12
|
+
* @returns true if user confirmed, false otherwise
|
|
13
|
+
*/
|
|
14
|
+
export declare function confirm(message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025, Salesforce, Inc.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2
|
|
4
|
+
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
|
|
5
|
+
*/
|
|
6
|
+
import * as readline from 'node:readline';
|
|
7
|
+
/**
|
|
8
|
+
* Simple yes/no confirmation prompt.
|
|
9
|
+
*
|
|
10
|
+
* Output goes to stderr so it doesn't interfere with structured stdout output.
|
|
11
|
+
*
|
|
12
|
+
* @param message - Prompt message (the hint is appended automatically)
|
|
13
|
+
* @param options - Options to control default behavior
|
|
14
|
+
* @returns true if user confirmed, false otherwise
|
|
15
|
+
*/
|
|
16
|
+
export async function confirm(message, options) {
|
|
17
|
+
const defaultYes = options?.defaultYes ?? false;
|
|
18
|
+
const hint = defaultYes ? '(Y/n)' : '(y/N)';
|
|
19
|
+
const rl = readline.createInterface({
|
|
20
|
+
input: process.stdin,
|
|
21
|
+
output: process.stderr,
|
|
22
|
+
});
|
|
23
|
+
return new Promise((resolve) => {
|
|
24
|
+
rl.question(`${message} ${hint} `, (answer) => {
|
|
25
|
+
rl.close();
|
|
26
|
+
const normalized = answer.trim().toLowerCase();
|
|
27
|
+
if (normalized === '') {
|
|
28
|
+
resolve(defaultYes);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
resolve(normalized === 'y' || normalized === 'yes');
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=confirm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confirm.js","sourceRoot":"","sources":["../../../src/ux/confirm.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAO1C;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,OAAe,EAAE,OAAwB;IACrE,MAAM,UAAU,GAAG,OAAO,EAAE,UAAU,IAAI,KAAK,CAAC;IAChD,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;IAE5C,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC;QAClC,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,MAAM,EAAE,OAAO,CAAC,MAAM;KACvB,CAAC,CAAC;IAEH,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,EAAE,CAAC,QAAQ,CAAC,GAAG,OAAO,IAAI,IAAI,GAAG,EAAE,CAAC,MAAM,EAAE,EAAE;YAC5C,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YAC/C,IAAI,UAAU,KAAK,EAAE,EAAE,CAAC;gBACtB,OAAO,CAAC,UAAU,CAAC,CAAC;YACtB,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,KAAK,CAAC,CAAC;YACtD,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
|