arc-1 0.6.9 → 0.7.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/README.md +12 -9
- package/bin/arc1-cli.js +10 -0
- package/bin/arc1.js +1 -1
- package/dist/adt/abapgit.d.ts +39 -0
- package/dist/adt/abapgit.d.ts.map +1 -0
- package/dist/adt/abapgit.js +333 -0
- package/dist/adt/abapgit.js.map +1 -0
- package/dist/adt/cds-impact.d.ts +35 -0
- package/dist/adt/cds-impact.d.ts.map +1 -1
- package/dist/adt/cds-impact.js +71 -0
- package/dist/adt/cds-impact.js.map +1 -1
- package/dist/adt/client.d.ts +4 -1
- package/dist/adt/client.d.ts.map +1 -1
- package/dist/adt/client.js +18 -5
- package/dist/adt/client.js.map +1 -1
- package/dist/adt/config.d.ts +1 -0
- package/dist/adt/config.d.ts.map +1 -1
- package/dist/adt/config.js +1 -0
- package/dist/adt/config.js.map +1 -1
- package/dist/adt/crud.d.ts.map +1 -1
- package/dist/adt/crud.js +74 -8
- package/dist/adt/crud.js.map +1 -1
- package/dist/adt/devtools.d.ts +39 -3
- package/dist/adt/devtools.d.ts.map +1 -1
- package/dist/adt/devtools.js +237 -25
- package/dist/adt/devtools.js.map +1 -1
- package/dist/adt/diagnostics.d.ts +69 -7
- package/dist/adt/diagnostics.d.ts.map +1 -1
- package/dist/adt/diagnostics.js +694 -36
- package/dist/adt/diagnostics.js.map +1 -1
- package/dist/adt/errors.d.ts +36 -2
- package/dist/adt/errors.d.ts.map +1 -1
- package/dist/adt/errors.js +111 -11
- package/dist/adt/errors.js.map +1 -1
- package/dist/adt/features.d.ts.map +1 -1
- package/dist/adt/features.js +3 -0
- package/dist/adt/features.js.map +1 -1
- package/dist/adt/gcts.d.ts +68 -0
- package/dist/adt/gcts.d.ts.map +1 -0
- package/dist/adt/gcts.js +239 -0
- package/dist/adt/gcts.js.map +1 -0
- package/dist/adt/http.d.ts.map +1 -1
- package/dist/adt/http.js +86 -1
- package/dist/adt/http.js.map +1 -1
- package/dist/adt/rap-handlers.d.ts +165 -0
- package/dist/adt/rap-handlers.d.ts.map +1 -0
- package/dist/adt/rap-handlers.js +835 -0
- package/dist/adt/rap-handlers.js.map +1 -0
- package/dist/adt/rap-preflight.d.ts +43 -0
- package/dist/adt/rap-preflight.d.ts.map +1 -0
- package/dist/adt/rap-preflight.js +405 -0
- package/dist/adt/rap-preflight.js.map +1 -0
- package/dist/adt/safety.d.ts +60 -33
- package/dist/adt/safety.d.ts.map +1 -1
- package/dist/adt/safety.js +204 -113
- package/dist/adt/safety.js.map +1 -1
- package/dist/adt/transport.d.ts +1 -1
- package/dist/adt/transport.d.ts.map +1 -1
- package/dist/adt/transport.js +6 -3
- package/dist/adt/transport.js.map +1 -1
- package/dist/adt/types.d.ts +225 -0
- package/dist/adt/types.d.ts.map +1 -1
- package/dist/adt/xml-parser.d.ts +15 -1
- package/dist/adt/xml-parser.d.ts.map +1 -1
- package/dist/adt/xml-parser.js +28 -15
- package/dist/adt/xml-parser.js.map +1 -1
- package/dist/authz/policy.d.ts +53 -0
- package/dist/authz/policy.d.ts.map +1 -0
- package/dist/authz/policy.js +199 -0
- package/dist/authz/policy.js.map +1 -0
- package/dist/cli-args.d.ts +14 -0
- package/dist/cli-args.d.ts.map +1 -0
- package/dist/cli-args.js +62 -0
- package/dist/cli-args.js.map +1 -0
- package/dist/cli.d.ts +13 -7
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +252 -55
- package/dist/cli.js.map +1 -1
- package/dist/extract-sap-cookies.d.ts +24 -0
- package/dist/extract-sap-cookies.d.ts.map +1 -0
- package/dist/extract-sap-cookies.js +317 -0
- package/dist/extract-sap-cookies.js.map +1 -0
- package/dist/handlers/hyperfocused.d.ts +4 -3
- package/dist/handlers/hyperfocused.d.ts.map +1 -1
- package/dist/handlers/hyperfocused.js +25 -16
- package/dist/handlers/hyperfocused.js.map +1 -1
- package/dist/handlers/intent.d.ts +4 -12
- package/dist/handlers/intent.d.ts.map +1 -1
- package/dist/handlers/intent.js +1448 -89
- package/dist/handlers/intent.js.map +1 -1
- package/dist/handlers/schemas.d.ts +83 -11
- package/dist/handlers/schemas.d.ts.map +1 -1
- package/dist/handlers/schemas.js +115 -4
- package/dist/handlers/schemas.js.map +1 -1
- package/dist/handlers/tools.d.ts +4 -3
- package/dist/handlers/tools.d.ts.map +1 -1
- package/dist/handlers/tools.js +342 -143
- package/dist/handlers/tools.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +7 -6
- package/dist/index.js.map +1 -1
- package/dist/probe/catalog.d.ts +30 -0
- package/dist/probe/catalog.d.ts.map +1 -0
- package/dist/probe/catalog.js +196 -0
- package/dist/probe/catalog.js.map +1 -0
- package/dist/probe/fixtures.d.ts +54 -0
- package/dist/probe/fixtures.d.ts.map +1 -0
- package/dist/probe/fixtures.js +94 -0
- package/dist/probe/fixtures.js.map +1 -0
- package/dist/probe/format.d.ts +10 -0
- package/dist/probe/format.d.ts.map +1 -0
- package/dist/probe/format.js +114 -0
- package/dist/probe/format.js.map +1 -0
- package/dist/probe/quality.d.ts +13 -0
- package/dist/probe/quality.d.ts.map +1 -0
- package/dist/probe/quality.js +50 -0
- package/dist/probe/quality.js.map +1 -0
- package/dist/probe/runner.d.ts +48 -0
- package/dist/probe/runner.d.ts.map +1 -0
- package/dist/probe/runner.js +211 -0
- package/dist/probe/runner.js.map +1 -0
- package/dist/probe/types.d.ts +159 -0
- package/dist/probe/types.d.ts.map +1 -0
- package/dist/probe/types.js +11 -0
- package/dist/probe/types.js.map +1 -0
- package/dist/server/audit.d.ts +26 -3
- package/dist/server/audit.d.ts.map +1 -1
- package/dist/server/audit.js +12 -1
- package/dist/server/audit.js.map +1 -1
- package/dist/server/config.d.ts +34 -19
- package/dist/server/config.d.ts.map +1 -1
- package/dist/server/config.js +327 -187
- package/dist/server/config.js.map +1 -1
- package/dist/server/deny-actions.d.ts +31 -0
- package/dist/server/deny-actions.d.ts.map +1 -0
- package/dist/server/deny-actions.js +156 -0
- package/dist/server/deny-actions.js.map +1 -0
- package/dist/server/effective-policy-log.d.ts +27 -0
- package/dist/server/effective-policy-log.d.ts.map +1 -0
- package/dist/server/effective-policy-log.js +103 -0
- package/dist/server/effective-policy-log.js.map +1 -0
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +15 -16
- package/dist/server/http.js.map +1 -1
- package/dist/server/server.d.ts +38 -4
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +234 -31
- package/dist/server/server.js.map +1 -1
- package/dist/server/types.d.ts +31 -13
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js +11 -10
- package/dist/server/types.js.map +1 -1
- package/dist/server/xsuaa.d.ts +1 -2
- package/dist/server/xsuaa.d.ts.map +1 -1
- package/dist/server/xsuaa.js +13 -14
- package/dist/server/xsuaa.js.map +1 -1
- package/package.json +9 -3
package/dist/server/config.js
CHANGED
|
@@ -4,14 +4,99 @@
|
|
|
4
4
|
* Resolves configuration from CLI flags, environment variables, and defaults.
|
|
5
5
|
* Priority: CLI > env > .env > defaults
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* Post-authz-refactor-v2 (v0.7):
|
|
8
|
+
* - Profile layer (`ARC1_PROFILE`) was removed. Use explicit `SAP_ALLOW_*` env vars.
|
|
9
|
+
* - Op-code allowlist/blocklist env vars (`SAP_ALLOWED_OPS` / `SAP_DISALLOWED_OPS`)
|
|
10
|
+
* were removed. Use `SAP_DENY_ACTIONS` for fine-grained per-action denials.
|
|
11
|
+
* - Single `ARC1_API_KEY` was removed. Use `ARC1_API_KEYS="key:profile"` instead.
|
|
12
|
+
* - Negated safety flags (`SAP_READ_ONLY`, `SAP_BLOCK_DATA`, `SAP_BLOCK_FREE_SQL`,
|
|
13
|
+
* `SAP_ENABLE_TRANSPORTS`, `SAP_ENABLE_GIT`) were replaced with positive opt-ins
|
|
14
|
+
* (`SAP_ALLOW_WRITES`, `SAP_ALLOW_DATA_PREVIEW`, `SAP_ALLOW_FREE_SQL`,
|
|
15
|
+
* `SAP_ALLOW_TRANSPORT_WRITES`, `SAP_ALLOW_GIT_WRITES`).
|
|
16
|
+
* - See docs_page/updating.md for the full migration table.
|
|
9
17
|
*/
|
|
18
|
+
import { parseDenyActions, validateDenyActions } from './deny-actions.js';
|
|
19
|
+
import { logger } from './logger.js';
|
|
10
20
|
import { DEFAULT_CONFIG } from './types.js';
|
|
21
|
+
export const API_KEY_PROFILES = {
|
|
22
|
+
viewer: {
|
|
23
|
+
scopes: ['read'],
|
|
24
|
+
safety: {
|
|
25
|
+
allowWrites: false,
|
|
26
|
+
allowDataPreview: false,
|
|
27
|
+
allowFreeSQL: false,
|
|
28
|
+
allowTransportWrites: false,
|
|
29
|
+
allowGitWrites: false,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
'viewer-data': {
|
|
33
|
+
scopes: ['read', 'data'],
|
|
34
|
+
safety: {
|
|
35
|
+
allowWrites: false,
|
|
36
|
+
allowDataPreview: true,
|
|
37
|
+
allowFreeSQL: false,
|
|
38
|
+
allowTransportWrites: false,
|
|
39
|
+
allowGitWrites: false,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
'viewer-sql': {
|
|
43
|
+
scopes: ['read', 'data', 'sql'],
|
|
44
|
+
safety: {
|
|
45
|
+
allowWrites: false,
|
|
46
|
+
allowDataPreview: true,
|
|
47
|
+
allowFreeSQL: true,
|
|
48
|
+
allowTransportWrites: false,
|
|
49
|
+
allowGitWrites: false,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
developer: {
|
|
53
|
+
scopes: ['read', 'write', 'transports', 'git'],
|
|
54
|
+
safety: {
|
|
55
|
+
allowWrites: true,
|
|
56
|
+
allowDataPreview: false,
|
|
57
|
+
allowFreeSQL: false,
|
|
58
|
+
allowTransportWrites: true,
|
|
59
|
+
allowGitWrites: true,
|
|
60
|
+
allowedPackages: ['$TMP'],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
'developer-data': {
|
|
64
|
+
scopes: ['read', 'write', 'data', 'transports', 'git'],
|
|
65
|
+
safety: {
|
|
66
|
+
allowWrites: true,
|
|
67
|
+
allowDataPreview: true,
|
|
68
|
+
allowFreeSQL: false,
|
|
69
|
+
allowTransportWrites: true,
|
|
70
|
+
allowGitWrites: true,
|
|
71
|
+
allowedPackages: ['$TMP'],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
'developer-sql': {
|
|
75
|
+
scopes: ['read', 'write', 'data', 'sql', 'transports', 'git'],
|
|
76
|
+
safety: {
|
|
77
|
+
allowWrites: true,
|
|
78
|
+
allowDataPreview: true,
|
|
79
|
+
allowFreeSQL: true,
|
|
80
|
+
allowTransportWrites: true,
|
|
81
|
+
allowGitWrites: true,
|
|
82
|
+
allowedPackages: ['$TMP'],
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
admin: {
|
|
86
|
+
scopes: ['read', 'write', 'data', 'sql', 'transports', 'git', 'admin'],
|
|
87
|
+
safety: {
|
|
88
|
+
allowWrites: true,
|
|
89
|
+
allowDataPreview: true,
|
|
90
|
+
allowFreeSQL: true,
|
|
91
|
+
allowTransportWrites: true,
|
|
92
|
+
allowGitWrites: true,
|
|
93
|
+
allowedPackages: [],
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
};
|
|
11
97
|
/**
|
|
12
98
|
* Parse API keys string into structured array.
|
|
13
99
|
* Format: "key1:profile1,key2:profile2"
|
|
14
|
-
* Each entry maps an API key to a named profile.
|
|
15
100
|
*/
|
|
16
101
|
export function parseApiKeys(raw) {
|
|
17
102
|
const entries = [];
|
|
@@ -19,20 +104,18 @@ export function parseApiKeys(raw) {
|
|
|
19
104
|
const trimmed = pair.trim();
|
|
20
105
|
if (!trimmed)
|
|
21
106
|
continue;
|
|
22
|
-
// Use LAST colon as separator — keys may contain colons (e.g. base64)
|
|
23
|
-
// but profile names never do
|
|
24
107
|
const colonIdx = trimmed.lastIndexOf(':');
|
|
25
108
|
if (colonIdx === -1) {
|
|
26
109
|
throw new Error(`Invalid API key entry '${trimmed}': expected 'key:profile' format. ` +
|
|
27
|
-
`Valid profiles: ${Object.keys(
|
|
110
|
+
`Valid profiles: ${Object.keys(API_KEY_PROFILES).join(', ')}`);
|
|
28
111
|
}
|
|
29
112
|
const key = trimmed.slice(0, colonIdx);
|
|
30
113
|
const profile = trimmed.slice(colonIdx + 1);
|
|
31
114
|
if (!key) {
|
|
32
115
|
throw new Error('Invalid API key entry: key cannot be empty');
|
|
33
116
|
}
|
|
34
|
-
if (!
|
|
35
|
-
throw new Error(`Invalid profile '${profile}' in API key entry. Valid profiles: ${Object.keys(
|
|
117
|
+
if (!API_KEY_PROFILES[profile]) {
|
|
118
|
+
throw new Error(`Invalid profile '${profile}' in API key entry. Valid profiles: ${Object.keys(API_KEY_PROFILES).join(', ')}`);
|
|
36
119
|
}
|
|
37
120
|
entries.push({ key, profile });
|
|
38
121
|
}
|
|
@@ -41,118 +124,150 @@ export function parseApiKeys(raw) {
|
|
|
41
124
|
}
|
|
42
125
|
return entries;
|
|
43
126
|
}
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
'
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
'
|
|
55
|
-
'developer-sql': ['read', 'write', 'data', 'sql'],
|
|
127
|
+
/** Map of legacy env-var names → human-readable migration hint. */
|
|
128
|
+
const LEGACY_ENV_VARS = {
|
|
129
|
+
SAP_READ_ONLY: 'Replaced by SAP_ALLOW_WRITES (inverted). Set SAP_ALLOW_WRITES=true to enable writes.',
|
|
130
|
+
SAP_BLOCK_DATA: 'Replaced by SAP_ALLOW_DATA_PREVIEW (inverted). Set SAP_ALLOW_DATA_PREVIEW=true to enable table preview.',
|
|
131
|
+
SAP_BLOCK_FREE_SQL: 'Replaced by SAP_ALLOW_FREE_SQL (inverted). Set SAP_ALLOW_FREE_SQL=true to enable freestyle SQL.',
|
|
132
|
+
SAP_ENABLE_TRANSPORTS: 'Replaced by SAP_ALLOW_TRANSPORT_WRITES. Transport reads are always available; writes need SAP_ALLOW_TRANSPORT_WRITES=true + SAP_ALLOW_WRITES=true.',
|
|
133
|
+
SAP_ENABLE_GIT: 'Replaced by SAP_ALLOW_GIT_WRITES. Git reads are always available; writes need SAP_ALLOW_GIT_WRITES=true + SAP_ALLOW_WRITES=true.',
|
|
134
|
+
SAP_ALLOWED_OPS: 'Op-code allowlist was removed. Use SAP_DENY_ACTIONS for fine-grained per-action denials (e.g., SAP_DENY_ACTIONS="SAPWrite.delete,SAPManage.flp_*").',
|
|
135
|
+
SAP_DISALLOWED_OPS: 'Op-code blocklist was removed. Use SAP_DENY_ACTIONS instead.',
|
|
136
|
+
ARC1_PROFILE: 'Server-side profile presets were removed. Set individual SAP_ALLOW_* flags (see .env.example for recipes).',
|
|
137
|
+
ARC1_API_KEY: 'Single API-key mode was removed. Use ARC1_API_KEYS="key:profile" with a profile name (valid: viewer, viewer-data, viewer-sql, developer, developer-data, developer-sql, admin).',
|
|
56
138
|
};
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
enableTransports: false,
|
|
68
|
-
},
|
|
69
|
-
'viewer-data': {
|
|
70
|
-
readOnly: true,
|
|
71
|
-
blockData: false,
|
|
72
|
-
blockFreeSQL: true,
|
|
73
|
-
enableTransports: false,
|
|
74
|
-
},
|
|
75
|
-
'viewer-sql': {
|
|
76
|
-
readOnly: true,
|
|
77
|
-
blockData: false,
|
|
78
|
-
blockFreeSQL: false,
|
|
79
|
-
enableTransports: false,
|
|
80
|
-
},
|
|
81
|
-
developer: {
|
|
82
|
-
readOnly: false,
|
|
83
|
-
blockData: true,
|
|
84
|
-
blockFreeSQL: true,
|
|
85
|
-
enableTransports: true,
|
|
86
|
-
allowedPackages: ['$TMP'],
|
|
87
|
-
},
|
|
88
|
-
'developer-data': {
|
|
89
|
-
readOnly: false,
|
|
90
|
-
blockData: false,
|
|
91
|
-
blockFreeSQL: true,
|
|
92
|
-
enableTransports: true,
|
|
93
|
-
allowedPackages: ['$TMP'],
|
|
94
|
-
},
|
|
95
|
-
'developer-sql': {
|
|
96
|
-
readOnly: false,
|
|
97
|
-
blockData: false,
|
|
98
|
-
blockFreeSQL: false,
|
|
99
|
-
enableTransports: true,
|
|
100
|
-
allowedPackages: ['$TMP'],
|
|
101
|
-
},
|
|
139
|
+
const LEGACY_CLI_FLAGS = {
|
|
140
|
+
'read-only': LEGACY_ENV_VARS.SAP_READ_ONLY,
|
|
141
|
+
'block-data': LEGACY_ENV_VARS.SAP_BLOCK_DATA,
|
|
142
|
+
'block-free-sql': LEGACY_ENV_VARS.SAP_BLOCK_FREE_SQL,
|
|
143
|
+
'enable-transports': LEGACY_ENV_VARS.SAP_ENABLE_TRANSPORTS,
|
|
144
|
+
'enable-git': LEGACY_ENV_VARS.SAP_ENABLE_GIT,
|
|
145
|
+
'allowed-ops': LEGACY_ENV_VARS.SAP_ALLOWED_OPS,
|
|
146
|
+
'disallowed-ops': LEGACY_ENV_VARS.SAP_DISALLOWED_OPS,
|
|
147
|
+
profile: LEGACY_ENV_VARS.ARC1_PROFILE,
|
|
148
|
+
'api-key': LEGACY_ENV_VARS.ARC1_API_KEY,
|
|
102
149
|
};
|
|
150
|
+
/** Migration guard — throws a helpful error if any legacy identifier is set. */
|
|
151
|
+
function detectLegacyConfig(args) {
|
|
152
|
+
const violations = [];
|
|
153
|
+
for (const env of Object.keys(LEGACY_ENV_VARS)) {
|
|
154
|
+
if (process.env[env] !== undefined) {
|
|
155
|
+
violations.push(` ${env}: ${LEGACY_ENV_VARS[env]}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
for (const flag of Object.keys(LEGACY_CLI_FLAGS)) {
|
|
159
|
+
if (args.some((a) => a === `--${flag}` || a.startsWith(`--${flag}=`))) {
|
|
160
|
+
violations.push(` --${flag}: ${LEGACY_CLI_FLAGS[flag]}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (violations.length > 0) {
|
|
164
|
+
throw new Error(`Legacy authorization config detected (removed in v0.7):\n${violations.join('\n')}\n\nSee docs_page/updating.md#v07-authorization-refactor-breaking-change for the full migration guide.`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
103
167
|
/**
|
|
104
|
-
* Parse CLI
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* the MCP server entry point needs to be fast and lightweight.
|
|
108
|
-
* Commander is used for the full CLI (cli.ts), not the server startup.
|
|
168
|
+
* Parse CLI args + env into a `{ config, sources }` pair.
|
|
169
|
+
* `sources` records where each field's value came from (default / env / flag / file).
|
|
170
|
+
* Consumed by the startup effective-policy log and the `arc1 config show` subcommand.
|
|
109
171
|
*/
|
|
110
|
-
export function
|
|
172
|
+
export function resolveConfig(args) {
|
|
173
|
+
detectLegacyConfig(args);
|
|
111
174
|
const config = { ...DEFAULT_CONFIG };
|
|
112
|
-
|
|
175
|
+
const sources = {};
|
|
176
|
+
// ── Resolvers ──────────────────────────────────────────────────────
|
|
113
177
|
const getFlag = (name) => {
|
|
114
178
|
const prefix = `--${name}=`;
|
|
115
179
|
for (let i = 0; i < args.length; i++) {
|
|
116
|
-
if (args[i] === `--${name}` && i + 1 < args.length)
|
|
180
|
+
if (args[i] === `--${name}` && i + 1 < args.length)
|
|
117
181
|
return args[i + 1];
|
|
118
|
-
|
|
119
|
-
if (args[i]?.startsWith(prefix)) {
|
|
182
|
+
if (args[i]?.startsWith(prefix))
|
|
120
183
|
return args[i].slice(prefix.length);
|
|
121
|
-
}
|
|
122
184
|
}
|
|
123
185
|
return undefined;
|
|
124
186
|
};
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
187
|
+
const resolveStr = (flag, envVar, defaultVal, fieldName) => {
|
|
188
|
+
const flagVal = getFlag(flag);
|
|
189
|
+
if (flagVal !== undefined) {
|
|
190
|
+
sources[fieldName] = { flag: `--${flag}` };
|
|
191
|
+
return flagVal;
|
|
192
|
+
}
|
|
193
|
+
if (process.env[envVar] !== undefined) {
|
|
194
|
+
sources[fieldName] = { env: envVar };
|
|
195
|
+
return process.env[envVar];
|
|
196
|
+
}
|
|
197
|
+
sources[fieldName] = 'default';
|
|
198
|
+
return defaultVal;
|
|
128
199
|
};
|
|
129
|
-
const resolveBool = (flag, envVar, defaultVal) => {
|
|
130
|
-
const
|
|
131
|
-
if (
|
|
132
|
-
|
|
133
|
-
|
|
200
|
+
const resolveBool = (flag, envVar, defaultVal, fieldName) => {
|
|
201
|
+
const flagVal = getFlag(flag);
|
|
202
|
+
if (flagVal !== undefined) {
|
|
203
|
+
sources[fieldName] = { flag: `--${flag}` };
|
|
204
|
+
return flagVal === 'true' || flagVal === '1';
|
|
205
|
+
}
|
|
206
|
+
if (process.env[envVar] !== undefined) {
|
|
207
|
+
sources[fieldName] = { env: envVar };
|
|
208
|
+
return process.env[envVar] === 'true' || process.env[envVar] === '1';
|
|
209
|
+
}
|
|
210
|
+
sources[fieldName] = 'default';
|
|
211
|
+
return defaultVal;
|
|
134
212
|
};
|
|
135
|
-
const resolveFeature = (flag, envVar) => {
|
|
136
|
-
const
|
|
137
|
-
if (
|
|
138
|
-
|
|
213
|
+
const resolveFeature = (flag, envVar, fieldName) => {
|
|
214
|
+
const flagVal = getFlag(flag);
|
|
215
|
+
if (flagVal !== undefined) {
|
|
216
|
+
sources[fieldName] = { flag: `--${flag}` };
|
|
217
|
+
if (flagVal === 'on' || flagVal === 'off')
|
|
218
|
+
return flagVal;
|
|
219
|
+
return 'auto';
|
|
220
|
+
}
|
|
221
|
+
const envVal = process.env[envVar];
|
|
222
|
+
if (envVal !== undefined) {
|
|
223
|
+
sources[fieldName] = { env: envVar };
|
|
224
|
+
if (envVal === 'on' || envVal === 'off')
|
|
225
|
+
return envVal;
|
|
226
|
+
return 'auto';
|
|
227
|
+
}
|
|
228
|
+
sources[fieldName] = 'default';
|
|
139
229
|
return 'auto';
|
|
140
230
|
};
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
231
|
+
const resolveOptionalStr = (flag, envVar, fieldName) => {
|
|
232
|
+
const flagVal = getFlag(flag);
|
|
233
|
+
if (flagVal !== undefined) {
|
|
234
|
+
sources[fieldName] = { flag: `--${flag}` };
|
|
235
|
+
return flagVal;
|
|
236
|
+
}
|
|
237
|
+
if (process.env[envVar] !== undefined) {
|
|
238
|
+
sources[fieldName] = { env: envVar };
|
|
239
|
+
return process.env[envVar];
|
|
240
|
+
}
|
|
241
|
+
sources[fieldName] = 'default';
|
|
242
|
+
return undefined;
|
|
243
|
+
};
|
|
244
|
+
// ── SAP Connection ─────────────────────────────────────────────────
|
|
245
|
+
config.url = resolveStr('url', 'SAP_URL', '', 'url');
|
|
246
|
+
config.username = resolveStr('user', 'SAP_USER', '', 'username');
|
|
247
|
+
config.password = resolveStr('password', 'SAP_PASSWORD', '', 'password');
|
|
248
|
+
config.client = resolveStr('client', 'SAP_CLIENT', '100', 'client');
|
|
249
|
+
config.language = resolveStr('language', 'SAP_LANGUAGE', 'EN', 'language');
|
|
250
|
+
config.insecure = resolveBool('insecure', 'SAP_INSECURE', false, 'insecure');
|
|
251
|
+
// ── Cookie Auth ────────────────────────────────────────────────────
|
|
252
|
+
config.cookieFile = resolveOptionalStr('cookie-file', 'SAP_COOKIE_FILE', 'cookieFile');
|
|
253
|
+
config.cookieString = resolveOptionalStr('cookie-string', 'SAP_COOKIE_STRING', 'cookieString');
|
|
254
|
+
// ── Transport ──────────────────────────────────────────────────────
|
|
255
|
+
const transport = resolveStr('transport', 'SAP_TRANSPORT', 'stdio', 'transport');
|
|
153
256
|
config.transport = (transport === 'http-streamable' ? 'http-streamable' : 'stdio');
|
|
154
|
-
|
|
155
|
-
|
|
257
|
+
const httpAddrFlag = getFlag('http-addr');
|
|
258
|
+
const httpAddrEnv = process.env.ARC1_HTTP_ADDR ?? process.env.SAP_HTTP_ADDR;
|
|
259
|
+
if (httpAddrFlag !== undefined) {
|
|
260
|
+
config.httpAddr = httpAddrFlag;
|
|
261
|
+
sources.httpAddr = { flag: '--http-addr' };
|
|
262
|
+
}
|
|
263
|
+
else if (httpAddrEnv !== undefined) {
|
|
264
|
+
config.httpAddr = httpAddrEnv;
|
|
265
|
+
sources.httpAddr = process.env.ARC1_HTTP_ADDR !== undefined ? { env: 'ARC1_HTTP_ADDR' } : { env: 'SAP_HTTP_ADDR' };
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
config.httpAddr = '0.0.0.0:8080';
|
|
269
|
+
sources.httpAddr = 'default';
|
|
270
|
+
}
|
|
156
271
|
const portOverride = getFlag('port') ?? process.env.ARC1_PORT;
|
|
157
272
|
if (portOverride) {
|
|
158
273
|
const parsedPort = Number.parseInt(portOverride, 10);
|
|
@@ -161,121 +276,147 @@ export function parseArgs(args) {
|
|
|
161
276
|
}
|
|
162
277
|
const addrHost = config.httpAddr.includes(':') ? config.httpAddr.split(':')[0] : '0.0.0.0';
|
|
163
278
|
config.httpAddr = `${addrHost}:${parsedPort}`;
|
|
279
|
+
sources.httpAddr = getFlag('port') !== undefined ? { flag: '--port' } : { env: 'ARC1_PORT' };
|
|
164
280
|
}
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
281
|
+
// ── Safety (positive opt-ins) ──────────────────────────────────────
|
|
282
|
+
config.allowWrites = resolveBool('allow-writes', 'SAP_ALLOW_WRITES', false, 'allowWrites');
|
|
283
|
+
config.allowDataPreview = resolveBool('allow-data-preview', 'SAP_ALLOW_DATA_PREVIEW', false, 'allowDataPreview');
|
|
284
|
+
config.allowFreeSQL = resolveBool('allow-free-sql', 'SAP_ALLOW_FREE_SQL', false, 'allowFreeSQL');
|
|
285
|
+
config.allowTransportWrites = resolveBool('allow-transport-writes', 'SAP_ALLOW_TRANSPORT_WRITES', false, 'allowTransportWrites');
|
|
286
|
+
config.allowGitWrites = resolveBool('allow-git-writes', 'SAP_ALLOW_GIT_WRITES', false, 'allowGitWrites');
|
|
287
|
+
const pkgs = getFlag('allowed-packages') ?? process.env.SAP_ALLOWED_PACKAGES;
|
|
288
|
+
if (pkgs !== undefined) {
|
|
289
|
+
const raw = pkgs.split(',').map((p) => p.trim());
|
|
290
|
+
const filtered = raw.filter((p) => p.length > 0);
|
|
291
|
+
if (raw.length !== filtered.length) {
|
|
292
|
+
logger.warn("SAP_ALLOWED_PACKAGES contained empty entries — likely shell expansion of unset $VARs. Use single quotes: SAP_ALLOWED_PACKAGES='$TMP,Z*'", { raw: pkgs, parsed: filtered });
|
|
171
293
|
}
|
|
172
|
-
|
|
294
|
+
config.allowedPackages = filtered;
|
|
295
|
+
sources.allowedPackages =
|
|
296
|
+
getFlag('allowed-packages') !== undefined ? { flag: '--allowed-packages' } : { env: 'SAP_ALLOWED_PACKAGES' };
|
|
173
297
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
config.
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
//
|
|
211
|
-
|
|
298
|
+
else {
|
|
299
|
+
sources.allowedPackages = 'default';
|
|
300
|
+
}
|
|
301
|
+
const transports = getFlag('allowed-transports') ?? process.env.SAP_ALLOWED_TRANSPORTS;
|
|
302
|
+
if (transports !== undefined) {
|
|
303
|
+
config.allowedTransports = transports
|
|
304
|
+
.split(',')
|
|
305
|
+
.map((t) => t.trim())
|
|
306
|
+
.filter((t) => t.length > 0);
|
|
307
|
+
sources.allowedTransports =
|
|
308
|
+
getFlag('allowed-transports') !== undefined
|
|
309
|
+
? { flag: '--allowed-transports' }
|
|
310
|
+
: { env: 'SAP_ALLOWED_TRANSPORTS' };
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
sources.allowedTransports = 'default';
|
|
314
|
+
}
|
|
315
|
+
// ── Deny Actions (parsed + validated; fails fast on error) ─────────
|
|
316
|
+
const denyActionsRaw = getFlag('deny-actions') ?? process.env.SAP_DENY_ACTIONS;
|
|
317
|
+
if (denyActionsRaw) {
|
|
318
|
+
const fromFile = denyActionsRaw.startsWith('/') ||
|
|
319
|
+
denyActionsRaw.startsWith('./') ||
|
|
320
|
+
denyActionsRaw.startsWith('~/') ||
|
|
321
|
+
denyActionsRaw.startsWith('../');
|
|
322
|
+
const parsed = parseDenyActions(denyActionsRaw);
|
|
323
|
+
validateDenyActions(parsed);
|
|
324
|
+
config.denyActions = parsed;
|
|
325
|
+
sources.denyActions = fromFile
|
|
326
|
+
? { file: denyActionsRaw.replace(/^~/, process.env.HOME ?? '~') }
|
|
327
|
+
: getFlag('deny-actions') !== undefined
|
|
328
|
+
? { flag: '--deny-actions' }
|
|
329
|
+
: { env: 'SAP_DENY_ACTIONS' };
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
sources.denyActions = 'default';
|
|
333
|
+
}
|
|
334
|
+
// ── Features ───────────────────────────────────────────────────────
|
|
335
|
+
config.featureAbapGit = resolveFeature('feature-abapgit', 'SAP_FEATURE_ABAPGIT', 'featureAbapGit');
|
|
336
|
+
config.featureGcts = resolveFeature('feature-gcts', 'SAP_FEATURE_GCTS', 'featureGcts');
|
|
337
|
+
config.featureRap = resolveFeature('feature-rap', 'SAP_FEATURE_RAP', 'featureRap');
|
|
338
|
+
config.featureAmdp = resolveFeature('feature-amdp', 'SAP_FEATURE_AMDP', 'featureAmdp');
|
|
339
|
+
config.featureUi5 = resolveFeature('feature-ui5', 'SAP_FEATURE_UI5', 'featureUi5');
|
|
340
|
+
config.featureTransport = resolveFeature('feature-transport', 'SAP_FEATURE_TRANSPORT', 'featureTransport');
|
|
341
|
+
config.featureHana = resolveFeature('feature-hana', 'SAP_FEATURE_HANA', 'featureHana');
|
|
342
|
+
config.featureUi5Repo = resolveFeature('feature-ui5repo', 'SAP_FEATURE_UI5REPO', 'featureUi5Repo');
|
|
343
|
+
config.featureFlp = resolveFeature('feature-flp', 'SAP_FEATURE_FLP', 'featureFlp');
|
|
344
|
+
// ── System Type Detection ──────────────────────────────────────────
|
|
345
|
+
const systemType = resolveStr('system-type', 'SAP_SYSTEM_TYPE', 'auto', 'systemType');
|
|
212
346
|
config.systemType = (['btp', 'onprem'].includes(systemType) ? systemType : 'auto');
|
|
213
|
-
//
|
|
214
|
-
config.apiKey = getFlag('api-key') ?? process.env.ARC1_API_KEY;
|
|
215
|
-
// Multiple API keys with per-key profiles: "key1:viewer,key2:developer"
|
|
347
|
+
// ── Authentication ─────────────────────────────────────────────────
|
|
216
348
|
const apiKeysRaw = getFlag('api-keys') ?? process.env.ARC1_API_KEYS;
|
|
217
349
|
if (apiKeysRaw) {
|
|
218
350
|
config.apiKeys = parseApiKeys(apiKeysRaw);
|
|
351
|
+
sources.apiKeys = getFlag('api-keys') !== undefined ? { flag: '--api-keys' } : { env: 'ARC1_API_KEYS' };
|
|
219
352
|
}
|
|
220
|
-
|
|
221
|
-
|
|
353
|
+
else {
|
|
354
|
+
sources.apiKeys = 'default';
|
|
355
|
+
}
|
|
356
|
+
config.oidcIssuer = resolveOptionalStr('oidc-issuer', 'SAP_OIDC_ISSUER', 'oidcIssuer');
|
|
357
|
+
config.oidcAudience = resolveOptionalStr('oidc-audience', 'SAP_OIDC_AUDIENCE', 'oidcAudience');
|
|
222
358
|
const clockTolerance = getFlag('oidc-clock-tolerance') ?? process.env.SAP_OIDC_CLOCK_TOLERANCE;
|
|
223
359
|
if (clockTolerance) {
|
|
224
360
|
const parsed = Number.parseInt(clockTolerance, 10);
|
|
225
361
|
config.oidcClockTolerance = Number.isNaN(parsed) ? undefined : parsed;
|
|
226
362
|
}
|
|
227
|
-
config.xsuaaAuth = resolveBool('xsuaa-auth', 'SAP_XSUAA_AUTH', false);
|
|
228
|
-
//
|
|
229
|
-
config.btpServiceKey =
|
|
230
|
-
config.btpServiceKeyFile =
|
|
231
|
-
const cbPort =
|
|
363
|
+
config.xsuaaAuth = resolveBool('xsuaa-auth', 'SAP_XSUAA_AUTH', false, 'xsuaaAuth');
|
|
364
|
+
// ── BTP ABAP Environment ───────────────────────────────────────────
|
|
365
|
+
config.btpServiceKey = resolveOptionalStr('btp-service-key', 'SAP_BTP_SERVICE_KEY', 'btpServiceKey');
|
|
366
|
+
config.btpServiceKeyFile = resolveOptionalStr('btp-service-key-file', 'SAP_BTP_SERVICE_KEY_FILE', 'btpServiceKeyFile');
|
|
367
|
+
const cbPort = resolveStr('btp-oauth-callback-port', 'SAP_BTP_OAUTH_CALLBACK_PORT', '0', 'btpOAuthCallbackPort');
|
|
232
368
|
config.btpOAuthCallbackPort = Number.parseInt(cbPort, 10) || 0;
|
|
233
|
-
//
|
|
234
|
-
config.ppEnabled = resolveBool('pp-enabled', 'SAP_PP_ENABLED', false);
|
|
235
|
-
config.ppStrict = resolveBool('pp-strict', 'SAP_PP_STRICT', false);
|
|
236
|
-
config.ppAllowSharedCookies = resolveBool('pp-allow-shared-cookies', 'SAP_PP_ALLOW_SHARED_COOKIES', false);
|
|
237
|
-
//
|
|
238
|
-
config.disableSaml2 = resolveBool('disable-saml', 'SAP_DISABLE_SAML', false);
|
|
239
|
-
//
|
|
240
|
-
const toolMode =
|
|
369
|
+
// ── Principal Propagation ──────────────────────────────────────────
|
|
370
|
+
config.ppEnabled = resolveBool('pp-enabled', 'SAP_PP_ENABLED', false, 'ppEnabled');
|
|
371
|
+
config.ppStrict = resolveBool('pp-strict', 'SAP_PP_STRICT', false, 'ppStrict');
|
|
372
|
+
config.ppAllowSharedCookies = resolveBool('pp-allow-shared-cookies', 'SAP_PP_ALLOW_SHARED_COOKIES', false, 'ppAllowSharedCookies');
|
|
373
|
+
// ── SAML Behavior ──────────────────────────────────────────────────
|
|
374
|
+
config.disableSaml2 = resolveBool('disable-saml', 'SAP_DISABLE_SAML', false, 'disableSaml2');
|
|
375
|
+
// ── Tool Mode ──────────────────────────────────────────────────────
|
|
376
|
+
const toolMode = resolveStr('tool-mode', 'ARC1_TOOL_MODE', 'standard', 'toolMode');
|
|
241
377
|
config.toolMode = (toolMode === 'hyperfocused' ? 'hyperfocused' : 'standard');
|
|
242
|
-
//
|
|
243
|
-
config.abaplintConfig =
|
|
244
|
-
config.lintBeforeWrite = resolveBool('lint-before-write', 'SAP_LINT_BEFORE_WRITE', true);
|
|
245
|
-
|
|
246
|
-
|
|
378
|
+
// ── Lint ───────────────────────────────────────────────────────────
|
|
379
|
+
config.abaplintConfig = resolveOptionalStr('abaplint-config', 'SAP_ABAPLINT_CONFIG', 'abaplintConfig');
|
|
380
|
+
config.lintBeforeWrite = resolveBool('lint-before-write', 'SAP_LINT_BEFORE_WRITE', true, 'lintBeforeWrite');
|
|
381
|
+
config.checkBeforeWrite = resolveBool('check-before-write', 'SAP_CHECK_BEFORE_WRITE', false, 'checkBeforeWrite');
|
|
382
|
+
// ── Cache ──────────────────────────────────────────────────────────
|
|
383
|
+
const cacheMode = resolveStr('cache', 'ARC1_CACHE', 'auto', 'cacheMode');
|
|
247
384
|
config.cacheMode = (['memory', 'sqlite', 'none'].includes(cacheMode) ? cacheMode : 'auto');
|
|
248
|
-
config.cacheFile =
|
|
249
|
-
config.cacheWarmup = resolveBool('cache-warmup', 'ARC1_CACHE_WARMUP', false);
|
|
250
|
-
config.cacheWarmupPackages =
|
|
251
|
-
//
|
|
385
|
+
config.cacheFile = resolveStr('cache-file', 'ARC1_CACHE_FILE', '.arc1-cache.db', 'cacheFile');
|
|
386
|
+
config.cacheWarmup = resolveBool('cache-warmup', 'ARC1_CACHE_WARMUP', false, 'cacheWarmup');
|
|
387
|
+
config.cacheWarmupPackages = resolveStr('cache-warmup-packages', 'ARC1_CACHE_WARMUP_PACKAGES', '', 'cacheWarmupPackages');
|
|
388
|
+
// ── Concurrency ────────────────────────────────────────────────────
|
|
252
389
|
const maxConcurrent = getFlag('max-concurrent') ?? process.env.ARC1_MAX_CONCURRENT;
|
|
253
390
|
if (maxConcurrent) {
|
|
254
391
|
const parsed = Number.parseInt(maxConcurrent, 10);
|
|
255
392
|
config.maxConcurrent = Number.isNaN(parsed) || parsed < 1 ? 1 : parsed;
|
|
256
393
|
}
|
|
257
|
-
//
|
|
258
|
-
config.logFile =
|
|
259
|
-
const logLevel =
|
|
394
|
+
// ── Logging ────────────────────────────────────────────────────────
|
|
395
|
+
config.logFile = resolveOptionalStr('log-file', 'ARC1_LOG_FILE', 'logFile');
|
|
396
|
+
const logLevel = resolveStr('log-level', 'ARC1_LOG_LEVEL', 'info', 'logLevel');
|
|
260
397
|
config.logLevel = (['debug', 'info', 'warn', 'error'].includes(logLevel) ? logLevel : 'info');
|
|
261
|
-
const logFormat =
|
|
398
|
+
const logFormat = resolveStr('log-format', 'ARC1_LOG_FORMAT', 'text', 'logFormat');
|
|
262
399
|
config.logFormat = (logFormat === 'json' ? 'json' : 'text');
|
|
263
|
-
//
|
|
264
|
-
config.verbose = resolveBool('verbose', 'SAP_VERBOSE', false);
|
|
265
|
-
|
|
266
|
-
if (config.verbose) {
|
|
400
|
+
// ── Misc ───────────────────────────────────────────────────────────
|
|
401
|
+
config.verbose = resolveBool('verbose', 'SAP_VERBOSE', false, 'verbose');
|
|
402
|
+
if (config.verbose)
|
|
267
403
|
config.logLevel = 'debug';
|
|
268
|
-
|
|
269
|
-
// --- Startup Validation ---
|
|
404
|
+
// ── Startup Validation ─────────────────────────────────────────────
|
|
270
405
|
validateConfig(config);
|
|
271
|
-
return config;
|
|
406
|
+
return { config, sources };
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Thin wrapper around `resolveConfig` that returns only the config object.
|
|
410
|
+
* Kept for callers that don't need per-field source attribution.
|
|
411
|
+
*/
|
|
412
|
+
export function parseArgs(args) {
|
|
413
|
+
return resolveConfig(args).config;
|
|
272
414
|
}
|
|
273
415
|
/**
|
|
274
416
|
* Validate configuration for internally consistent auth settings.
|
|
275
417
|
* Fails fast at startup for invalid or dangerous config combinations.
|
|
276
418
|
*/
|
|
277
419
|
export function validateConfig(config) {
|
|
278
|
-
// OIDC: audience is required when issuer is set (RFC 9700 §2.3 audience restriction)
|
|
279
420
|
if (config.oidcIssuer && !config.oidcAudience) {
|
|
280
421
|
throw new Error('SAP_OIDC_AUDIENCE is required when SAP_OIDC_ISSUER is set — ' +
|
|
281
422
|
'audience validation prevents token confusion across services (RFC 9700 §2.3)');
|
|
@@ -283,7 +424,6 @@ export function validateConfig(config) {
|
|
|
283
424
|
if (config.oidcAudience && !config.oidcIssuer) {
|
|
284
425
|
throw new Error('SAP_OIDC_ISSUER is required when SAP_OIDC_AUDIENCE is set');
|
|
285
426
|
}
|
|
286
|
-
// PP: ppStrict requires ppEnabled
|
|
287
427
|
if (config.ppStrict && !config.ppEnabled) {
|
|
288
428
|
throw new Error('SAP_PP_STRICT=true requires SAP_PP_ENABLED=true — strict mode has no effect without principal propagation enabled');
|
|
289
429
|
}
|