@aifabrix/builder 2.50.0 → 2.51.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/.cursor/plans/157.1-security-quality-violations-by-type.csv +374 -0
- package/.cursor/rules/assistant-driven-integration-build.mdc +40 -0
- package/integration/hubspot-test/create-hubspot.js +1 -16
- package/lib/api/evidence-kinds.api.js +128 -0
- package/lib/api/pipeline.api.js +5 -1
- package/lib/api/resource-types.api.js +4 -4
- package/lib/api/roles.api.js +121 -0
- package/lib/api/types/evidence-kinds.types.js +42 -0
- package/lib/api/types/roles.types.js +36 -0
- package/lib/app/certification-verify-rows.js +1 -1
- package/lib/app/show.js +2 -2
- package/lib/certification/merge-certification-from-artifact.js +1 -1
- package/lib/channels/channel-artifact-generator.js +2 -1
- package/lib/cli/index.js +4 -0
- package/lib/cli/setup-app.js +22 -5
- package/lib/cli/setup-dev.js +23 -7
- package/lib/cli/setup-utility-help-after.js +12 -0
- package/lib/cli/setup-utility-validate.js +41 -2
- package/lib/commands/deploy-gate-failure-details.js +21 -0
- package/lib/commands/deploy-gate-orchestrator.js +64 -18
- package/lib/commands/dev-cli-handlers.js +6 -3
- package/lib/commands/dev-init.js +7 -109
- package/lib/commands/dev-refresh.js +197 -0
- package/lib/commands/evidence-kind.js +428 -0
- package/lib/commands/governance-command-action.js +4 -2
- package/lib/commands/repair-openapi-export.js +1 -1
- package/lib/commands/repair-openapi-sync.js +14 -6
- package/lib/commands/resource-type.js +101 -18
- package/lib/commands/role.js +452 -0
- package/lib/commands/setup.js +1 -1
- package/lib/commands/validate-json.js +230 -0
- package/lib/commands/verify-operations-step-plan.js +78 -40
- package/lib/core/audit-logger.js +1 -1
- package/lib/core/secrets-load.js +1 -3
- package/lib/datasource/capability/relate-operations.js +1 -1
- package/lib/generator/helpers.js +13 -5
- package/lib/generator/split-variables.js +9 -3
- package/lib/infrastructure/helpers-docker-check.js +6 -0
- package/lib/infrastructure/helpers.js +1 -1
- package/lib/programmatic/auth-config.js +3 -2
- package/lib/programmatic/bearer-auth.js +1 -1
- package/lib/programmatic/enterprise-mcp-client.js +18 -2
- package/lib/programmatic/manifest-section-registry.js +18 -1
- package/lib/programmatic/openapi-result-schemas.js +28 -11
- package/lib/programmatic/openapi-schema-descriptions-core.js +5 -2
- package/lib/programmatic/openapi-schema-helpers.js +16 -1
- package/lib/programmatic/openapi-spec.js +1 -1
- package/lib/programmatic/run-repository-emcp.js +11 -3
- package/lib/programmatic/run-template-workspace.js +14 -1
- package/lib/resolvers/evidence-kind-file.js +230 -0
- package/lib/resolvers/resource-type-file.js +36 -1
- package/lib/resolvers/role-file.js +227 -0
- package/lib/schema/application-schema.json +22 -2
- package/lib/schema/external-datasource.schema.json +85 -2
- package/lib/schema/external-system.schema.json +16 -2
- package/lib/schema/infra.parameter.yaml +25 -1
- package/lib/schema/type/credential-field-registry.json +19 -3
- package/lib/utils/api.js +8 -2
- package/lib/utils/app-register-config.js +4 -3
- package/lib/utils/cli-sync-options.js +59 -3
- package/lib/utils/cli-utils.js +11 -3
- package/lib/utils/dev-ca-install.js +5 -6
- package/lib/utils/dev-config.js +2 -2
- package/lib/utils/dev-list-sort.js +145 -0
- package/lib/utils/dev-platform-tls.js +0 -63
- package/lib/utils/docker-build.js +46 -27
- package/lib/utils/docker-daemon-tls-ca.js +1 -1
- package/lib/utils/docker-exec.js +91 -6
- package/lib/utils/env-copy.js +1 -3
- package/lib/utils/error-formatters/error-parser.js +0 -3
- package/lib/utils/external-env-template.js +1 -1
- package/lib/utils/external-system-readiness-compact-blocks.js +28 -1
- package/lib/utils/health-check-request.js +2 -3
- package/lib/utils/help-builder.js +3 -0
- package/lib/utils/image-name.js +22 -4
- package/lib/utils/logger.js +37 -4
- package/lib/utils/manifest-source-emit.js +1 -1
- package/lib/utils/paths.js +32 -4
- package/lib/utils/pipeline-upload-auth-retry.js +4 -0
- package/lib/utils/postgres-platform-bootstrap.js +24 -17
- package/lib/utils/postgres-wipe.js +5 -2
- package/lib/utils/remote-dev-auth.js +39 -4
- package/lib/utils/resolve-docker-image-ref.js +36 -6
- package/lib/utils/token-manager.js +16 -0
- package/lib/utils/upload-sync-options.js +17 -3
- package/lib/validation/openapi-contract-surface-validator.js +4 -0
- package/lib/validation/validator.js +6 -5
- package/package.json +34 -12
- package/scripts/export-builder-api-evidence.js +2 -2
- package/scripts/lib/plan-actor.js +2 -2
- package/scripts/lib/plan-meta.js +1 -1
- package/scripts/lib/plan-parse.js +1 -1
- package/scripts/lib/plan-prevalidate-gates.js +12 -1
- package/scripts/lib/plan-run-report.js +13 -2
- package/scripts/pnpm/lib/resolve-aifabrix-bin.mjs +10 -10
- package/templates/applications/dataplane/application.yaml +26 -4
- package/templates/applications/dataplane/env.template +28 -9
- package/templates/applications/dataplane/rbac.yaml +64 -2
- package/templates/applications/miso-controller/application.yaml +10 -1
- package/templates/applications/miso-controller/env.template +10 -7
- package/templates/applications/miso-controller/rbac.yaml +72 -0
- package/templates/channels/slack/integration/slack-system.json +60 -2
- package/templates/python/requirements.txt +1 -1
|
@@ -110,25 +110,15 @@ function buildDockerCliArgs(imageName, tag, dockerfilePath, contextPath, buildAr
|
|
|
110
110
|
return args;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
function
|
|
114
|
-
const {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
env = {},
|
|
123
|
-
buildArgs = {},
|
|
124
|
-
noCache = false
|
|
125
|
-
} = buildOpts;
|
|
126
|
-
const spawnEnv = { ...process.env, ...env };
|
|
127
|
-
const args = buildDockerCliArgs(imageName, tag, dockerfilePath, contextPath, buildArgs, noCache);
|
|
128
|
-
const dockerProcess = spawn('docker', args, {
|
|
129
|
-
shell: process.platform === 'win32',
|
|
130
|
-
env: spawnEnv
|
|
131
|
-
});
|
|
113
|
+
function createDockerSpawnParams(options) {
|
|
114
|
+
const { imageName, tag, dockerfilePath, contextPath, buildArgs, noCache, env } = options;
|
|
115
|
+
const spawnEnv = { ...process.env, ...(env || {}) };
|
|
116
|
+
const args = buildDockerCliArgs(imageName, tag, dockerfilePath, contextPath, buildArgs || {}, noCache);
|
|
117
|
+
return { spawnEnv, args };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function attachDockerBuildListeners(dockerProcess, context) {
|
|
121
|
+
const { imageName, tag, spinner, resolve, reject } = context;
|
|
132
122
|
let stdoutBuffer = '';
|
|
133
123
|
let stderrBuffer = '';
|
|
134
124
|
const lastProgressUpdateRef = { current: Date.now() };
|
|
@@ -162,6 +152,35 @@ function runDockerBuildProcess(buildOpts) {
|
|
|
162
152
|
});
|
|
163
153
|
}
|
|
164
154
|
|
|
155
|
+
function runDockerBuildProcess(buildOpts) {
|
|
156
|
+
const {
|
|
157
|
+
imageName,
|
|
158
|
+
tag,
|
|
159
|
+
dockerfilePath,
|
|
160
|
+
contextPath,
|
|
161
|
+
spinner,
|
|
162
|
+
resolve,
|
|
163
|
+
reject,
|
|
164
|
+
env = {},
|
|
165
|
+
buildArgs = {},
|
|
166
|
+
noCache = false
|
|
167
|
+
} = buildOpts;
|
|
168
|
+
const { spawnEnv, args } = createDockerSpawnParams({
|
|
169
|
+
imageName,
|
|
170
|
+
tag,
|
|
171
|
+
dockerfilePath,
|
|
172
|
+
contextPath,
|
|
173
|
+
buildArgs,
|
|
174
|
+
noCache,
|
|
175
|
+
env
|
|
176
|
+
});
|
|
177
|
+
const dockerProcess = spawn('docker', args, {
|
|
178
|
+
shell: process.platform === 'win32',
|
|
179
|
+
env: spawnEnv
|
|
180
|
+
});
|
|
181
|
+
attachDockerBuildListeners(dockerProcess, { imageName, tag, spinner, resolve, reject });
|
|
182
|
+
}
|
|
183
|
+
|
|
165
184
|
/**
|
|
166
185
|
* Executes Docker build command with progress indicator
|
|
167
186
|
* @param {string} imageName - Image name to build
|
|
@@ -236,12 +255,10 @@ async function executeBuild(imageName, dockerfilePath, contextPath, tag, options
|
|
|
236
255
|
|
|
237
256
|
// Tag image if additional tag provided
|
|
238
257
|
if (options && options.tag && options.tag !== 'latest') {
|
|
239
|
-
const {
|
|
240
|
-
const { exec } = require('child_process');
|
|
241
|
-
const run = promisify(exec);
|
|
258
|
+
const { execFileWithDockerEnv } = require('./docker-exec');
|
|
242
259
|
const { getDockerExecEnv } = require('./remote-docker-env');
|
|
243
260
|
const env = await getDockerExecEnv();
|
|
244
|
-
await
|
|
261
|
+
await execFileWithDockerEnv('docker', ['tag', `${imageName}:${tag}`, `${imageName}:latest`], { env });
|
|
245
262
|
}
|
|
246
263
|
}
|
|
247
264
|
|
|
@@ -270,12 +287,14 @@ async function executeDockerBuildWithTag(effectiveImageName, imageName, dockerfi
|
|
|
270
287
|
}
|
|
271
288
|
|
|
272
289
|
try {
|
|
273
|
-
const {
|
|
274
|
-
const { exec } = require('child_process');
|
|
275
|
-
const run = promisify(exec);
|
|
290
|
+
const { execFileWithDockerEnv } = require('./docker-exec');
|
|
276
291
|
const { getDockerExecEnv } = require('./remote-docker-env');
|
|
277
292
|
const env = await getDockerExecEnv();
|
|
278
|
-
await
|
|
293
|
+
await execFileWithDockerEnv(
|
|
294
|
+
'docker',
|
|
295
|
+
['tag', `${effectiveImageName}:${tag}`, `${imageName}:${tag}`],
|
|
296
|
+
{ env }
|
|
297
|
+
);
|
|
279
298
|
logger.log(formatSuccessLine(`Tagged image: ${imageName}:${tag}`));
|
|
280
299
|
} catch (err) {
|
|
281
300
|
logger.log(
|
package/lib/utils/docker-exec.js
CHANGED
|
@@ -9,9 +9,85 @@
|
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
11
|
const { promisify } = require('util');
|
|
12
|
-
const
|
|
12
|
+
const childProcess = require('child_process');
|
|
13
|
+
const execFileAsync = childProcess.execFile ? promisify(childProcess.execFile) : null;
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Parse a command string into argv tokens without invoking a shell.
|
|
17
|
+
* Supports simple single/double quoted segments and escaping quotes within double quotes.
|
|
18
|
+
* @param {string} command
|
|
19
|
+
* @returns {string[]}
|
|
20
|
+
*/
|
|
21
|
+
// eslint-disable-next-line max-statements, complexity -- tokenizer intentionally handles quoted token edge-cases in one place
|
|
22
|
+
function parseCommandTokens(command) {
|
|
23
|
+
const input = String(command || '');
|
|
24
|
+
if (!input.trim()) {
|
|
25
|
+
throw new Error('Command must be a non-empty string');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** @type {string[]} */
|
|
29
|
+
const tokens = [];
|
|
30
|
+
let current = '';
|
|
31
|
+
let quote = null;
|
|
32
|
+
|
|
33
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
34
|
+
const ch = input[i];
|
|
35
|
+
if (quote === null && (ch === '"' || ch === '\'')) {
|
|
36
|
+
quote = ch;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (quote !== null && ch === quote) {
|
|
40
|
+
quote = null;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
// Bash double-quote escapes: \", \\, \$, \`, and escaped newlines.
|
|
44
|
+
if (quote === '"' && ch === '\\') {
|
|
45
|
+
const next = input[i + 1];
|
|
46
|
+
if (next === '"' || next === '\\' || next === '$' || next === '`' || next === '\n') {
|
|
47
|
+
current += next;
|
|
48
|
+
i += 1;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (quote === null && /\s/.test(ch)) {
|
|
53
|
+
if (current) {
|
|
54
|
+
tokens.push(current);
|
|
55
|
+
current = '';
|
|
56
|
+
}
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
current += ch;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (quote !== null) {
|
|
63
|
+
throw new Error('Unterminated quote in command');
|
|
64
|
+
}
|
|
65
|
+
if (current) {
|
|
66
|
+
tokens.push(current);
|
|
67
|
+
}
|
|
68
|
+
if (!tokens.length) {
|
|
69
|
+
throw new Error('Command produced no executable token');
|
|
70
|
+
}
|
|
71
|
+
return tokens;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Run an executable with argv and remote Docker env settings.
|
|
76
|
+
* @param {string} file
|
|
77
|
+
* @param {string[]} [args]
|
|
78
|
+
* @param {import('child_process').ExecFileOptions} [options]
|
|
79
|
+
* @returns {Promise<{ stdout: string, stderr: string }>}
|
|
80
|
+
*/
|
|
81
|
+
async function execFileWithDockerEnv(file, args = [], options = {}) {
|
|
82
|
+
const { getDockerExecEnv } = require('./remote-docker-env');
|
|
83
|
+
const env = { ...(await getDockerExecEnv()), ...(options.env || {}) };
|
|
84
|
+
if (execFileAsync) {
|
|
85
|
+
return execFileAsync(file, args, { ...options, env });
|
|
86
|
+
}
|
|
87
|
+
const execAsync = promisify(childProcess.exec);
|
|
88
|
+
const command = [file, ...args.map((arg) => String(arg))].join(' ');
|
|
89
|
+
return execAsync(command, { ...options, env });
|
|
90
|
+
}
|
|
15
91
|
|
|
16
92
|
/**
|
|
17
93
|
* Run a shell command with process.env merged with remote Docker client settings when docker-endpoint is set.
|
|
@@ -20,9 +96,18 @@ const execAsync = promisify(exec);
|
|
|
20
96
|
* @returns {Promise<{ stdout: string, stderr: string }>}
|
|
21
97
|
*/
|
|
22
98
|
async function execWithDockerEnv(command, options = {}) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
99
|
+
if (!execFileAsync || process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID !== undefined) {
|
|
100
|
+
const { getDockerExecEnv } = require('./remote-docker-env');
|
|
101
|
+
const env = { ...(await getDockerExecEnv()), ...(options.env || {}) };
|
|
102
|
+
const execAsync = promisify(childProcess.exec);
|
|
103
|
+
return execAsync(command, { ...options, env });
|
|
104
|
+
}
|
|
105
|
+
const [file, ...args] = parseCommandTokens(command);
|
|
106
|
+
return execFileWithDockerEnv(file, args, options);
|
|
26
107
|
}
|
|
27
108
|
|
|
28
|
-
module.exports = {
|
|
109
|
+
module.exports = {
|
|
110
|
+
execWithDockerEnv,
|
|
111
|
+
execFileWithDockerEnv,
|
|
112
|
+
parseCommandTokens
|
|
113
|
+
};
|
package/lib/utils/env-copy.js
CHANGED
|
@@ -385,9 +385,7 @@ async function processEnvVariables(envPath, variablesPath, appName, secretsPath,
|
|
|
385
385
|
}
|
|
386
386
|
|
|
387
387
|
const label = variables.build.envOutputPath;
|
|
388
|
-
const appPathForLocal =
|
|
389
|
-
(copyOptions && copyOptions.appPath) ||
|
|
390
|
-
(variablesPath ? path.dirname(variablesPath) : null);
|
|
388
|
+
const appPathForLocal = (copyOptions && copyOptions.appPath) || path.dirname(variablesPath);
|
|
391
389
|
const preferLocal =
|
|
392
390
|
copyOptions && typeof copyOptions === 'object' && copyOptions.preferLocalEnvOutputPath === true;
|
|
393
391
|
const freshEnv = copyOptions && typeof copyOptions === 'object' && copyOptions.freshEnv === true;
|
|
@@ -25,9 +25,6 @@ const { formatNetworkError } = require('./network-errors');
|
|
|
25
25
|
* @returns {Object} Parsed error data object
|
|
26
26
|
*/
|
|
27
27
|
function parseErrorData(errorResponse) {
|
|
28
|
-
if (errorResponse === undefined || errorResponse === null) {
|
|
29
|
-
return { message: 'Unknown error occurred' };
|
|
30
|
-
}
|
|
31
28
|
if (typeof errorResponse === 'string') {
|
|
32
29
|
try {
|
|
33
30
|
return JSON.parse(errorResponse);
|
|
@@ -142,7 +142,7 @@ function buildConfigurationEntries(system) {
|
|
|
142
142
|
function buildExternalEnvTemplateContext(system) {
|
|
143
143
|
const authBlock = system?.authentication || system?.auth;
|
|
144
144
|
const credential = getAuthCredentialTemplate(authBlock);
|
|
145
|
-
let authMethod
|
|
145
|
+
let authMethod;
|
|
146
146
|
if (authBlock?.authType === 'apiProvider') {
|
|
147
147
|
authMethod = 'apiprovider';
|
|
148
148
|
} else if (authBlock?.authType === 'none') {
|
|
@@ -215,7 +215,34 @@ function logDeployReadinessSummaryCompact(ctx) {
|
|
|
215
215
|
logCompactTip({ systemKey, command: 'deploy', probed: !!ctx.probeData });
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Compact abort card when a deploy --gate pillar fails before controller deploy.
|
|
220
|
+
* @param {Object} ctx
|
|
221
|
+
*/
|
|
222
|
+
function logDeployGateAbortCompact(ctx) {
|
|
223
|
+
const systemKey = ctx.systemKey || 'system';
|
|
224
|
+
logCompactHeader({
|
|
225
|
+
command: 'deploy',
|
|
226
|
+
systemKey,
|
|
227
|
+
environment: ctx.environment || 'dev',
|
|
228
|
+
dataplaneUrl: ctx.dataplaneUrl
|
|
229
|
+
});
|
|
230
|
+
logger.log(` ${formatSuccessLine('Local validation')}`);
|
|
231
|
+
const pf = formatPreflightLine(ctx.baseline || { unitPassed: true, integrationPassed: true });
|
|
232
|
+
if (pf) logger.log(` ${pf}`);
|
|
233
|
+
for (const gate of ctx.gatesSoFar || []) {
|
|
234
|
+
logger.log(` ${formatGatePillarLine(gate)}`);
|
|
235
|
+
}
|
|
236
|
+
const pillar = ctx.failedPillar || 'operations';
|
|
237
|
+
const detail = ctx.failedDetail || 'gate failed';
|
|
238
|
+
const label = PILLAR_LABELS[pillar] || pillar;
|
|
239
|
+
logger.log(` ${formatBlockingError(`${label} · FAILED — ${detail}`)}`);
|
|
240
|
+
logger.log('');
|
|
241
|
+
logCompactTip({ systemKey, command: 'deploy', probed: false });
|
|
242
|
+
}
|
|
243
|
+
|
|
218
244
|
module.exports = {
|
|
219
245
|
logUploadReadinessSummaryCompact,
|
|
220
|
-
logDeployReadinessSummaryCompact
|
|
246
|
+
logDeployReadinessSummaryCompact,
|
|
247
|
+
logDeployGateAbortCompact
|
|
221
248
|
};
|
|
@@ -50,7 +50,7 @@ function handleHealthResponse(res, data, debug, resolve) {
|
|
|
50
50
|
resolve(isHealthy);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
function doHealthCheckRequest(healthCheckUrl, debug, resolve, reject,
|
|
53
|
+
function doHealthCheckRequest(healthCheckUrl, debug, resolve, reject, _allowInsecureTls = false) {
|
|
54
54
|
try {
|
|
55
55
|
const urlObj = new URL(healthCheckUrl);
|
|
56
56
|
const transport = urlObj.protocol === 'https:' ? https : http;
|
|
@@ -58,8 +58,7 @@ function doHealthCheckRequest(healthCheckUrl, debug, resolve, reject, allowInsec
|
|
|
58
58
|
hostname: urlObj.hostname,
|
|
59
59
|
port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
|
|
60
60
|
path: urlObj.pathname + urlObj.search,
|
|
61
|
-
method: 'GET'
|
|
62
|
-
...(allowInsecureTls ? { rejectUnauthorized: false } : {})
|
|
61
|
+
method: 'GET'
|
|
63
62
|
};
|
|
64
63
|
if (debug) {
|
|
65
64
|
logger.log(chalk.gray(`[DEBUG] Health check request: ${healthCheckUrl}`));
|
|
@@ -78,6 +78,8 @@ const CATEGORIES = [
|
|
|
78
78
|
{ name: 'dimension' },
|
|
79
79
|
{ name: 'dimension value', term: 'dimension value <command>' },
|
|
80
80
|
{ name: 'resource-type' },
|
|
81
|
+
{ name: 'role' },
|
|
82
|
+
{ name: 'evidence-kind' },
|
|
81
83
|
{ name: 'enterprise-knowledge' },
|
|
82
84
|
{ name: 'identity' },
|
|
83
85
|
{ name: 'protection' },
|
|
@@ -93,6 +95,7 @@ const CATEGORIES = [
|
|
|
93
95
|
{ name: 'convert', term: 'convert <appKey|systemKey>' },
|
|
94
96
|
{ name: 'show', term: 'show <appKey|systemKey>' },
|
|
95
97
|
{ name: 'validate', term: 'validate [appKey|systemKey|file]' },
|
|
98
|
+
{ name: 'validate-json', term: 'validate-json [appKey|systemKey]' },
|
|
96
99
|
{ name: 'parameters', term: 'parameters validate' },
|
|
97
100
|
{ name: 'diff', term: 'diff <file1> <file2>' }
|
|
98
101
|
]
|
package/lib/utils/image-name.js
CHANGED
|
@@ -9,6 +9,23 @@
|
|
|
9
9
|
|
|
10
10
|
'use strict';
|
|
11
11
|
|
|
12
|
+
const DOCKER_REPOSITORY_PATH_RE = /^[a-z0-9][a-z0-9._/-]*$/i;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} repositoryPath
|
|
16
|
+
* @returns {string}
|
|
17
|
+
*/
|
|
18
|
+
function assertSafeRepositoryPath(repositoryPath) {
|
|
19
|
+
const normalized = String(repositoryPath || '').trim();
|
|
20
|
+
if (!normalized) {
|
|
21
|
+
throw new Error('Repository path is required');
|
|
22
|
+
}
|
|
23
|
+
if (!DOCKER_REPOSITORY_PATH_RE.test(normalized)) {
|
|
24
|
+
throw new Error(`Repository path contains unsafe characters: ${repositoryPath}`);
|
|
25
|
+
}
|
|
26
|
+
return normalized;
|
|
27
|
+
}
|
|
28
|
+
|
|
12
29
|
/**
|
|
13
30
|
* Builds a developer-scoped image name for local Docker builds.
|
|
14
31
|
* Format: "<base>-dev<developerId>" when developerId is a positive integer.
|
|
@@ -55,18 +72,19 @@ function buildDevImageRepositoryPath(repositoryPath, developerId) {
|
|
|
55
72
|
if (!repositoryPath || typeof repositoryPath !== 'string') {
|
|
56
73
|
throw new Error('Repository path is required and must be a string');
|
|
57
74
|
}
|
|
75
|
+
const safeRepositoryPath = assertSafeRepositoryPath(repositoryPath);
|
|
58
76
|
const idNum =
|
|
59
77
|
typeof developerId === 'number' ? developerId : parseInt(String(developerId), 10);
|
|
60
78
|
if (!Number.isFinite(idNum) || idNum === 0) {
|
|
61
|
-
return
|
|
79
|
+
return safeRepositoryPath;
|
|
62
80
|
}
|
|
63
|
-
const idx =
|
|
64
|
-
const tail = idx === -1 ?
|
|
81
|
+
const idx = safeRepositoryPath.lastIndexOf('/');
|
|
82
|
+
const tail = idx === -1 ? safeRepositoryPath : safeRepositoryPath.slice(idx + 1);
|
|
65
83
|
const scopedTail = buildDevImageName(tail, developerId);
|
|
66
84
|
if (idx === -1) {
|
|
67
85
|
return scopedTail;
|
|
68
86
|
}
|
|
69
|
-
return `${
|
|
87
|
+
return `${safeRepositoryPath.slice(0, idx)}/${scopedTail}`;
|
|
70
88
|
}
|
|
71
89
|
|
|
72
90
|
module.exports = {
|
package/lib/utils/logger.js
CHANGED
|
@@ -11,6 +11,39 @@
|
|
|
11
11
|
|
|
12
12
|
/* eslint-disable no-console */
|
|
13
13
|
|
|
14
|
+
const SENSITIVE_KEY_RE = /(password|secret|token|apikey|apiKey|authorization|clientSecret|x-client-token)/i;
|
|
15
|
+
const BEARER_TOKEN_RE = /(Bearer\s+)[A-Za-z0-9._~-]+/gi;
|
|
16
|
+
|
|
17
|
+
function maskString(value) {
|
|
18
|
+
const asString = String(value);
|
|
19
|
+
return asString.replace(BEARER_TOKEN_RE, '$1[REDACTED]');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function sanitizeValue(value) {
|
|
23
|
+
if (value instanceof Error) {
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
if (typeof value === 'string') {
|
|
27
|
+
return maskString(value);
|
|
28
|
+
}
|
|
29
|
+
if (Array.isArray(value)) {
|
|
30
|
+
return value.map((item) => sanitizeValue(item));
|
|
31
|
+
}
|
|
32
|
+
if (value && typeof value === 'object') {
|
|
33
|
+
/** @type {Record<string, any>} */
|
|
34
|
+
const out = {};
|
|
35
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
36
|
+
if (SENSITIVE_KEY_RE.test(key)) {
|
|
37
|
+
out[key] = '[REDACTED]';
|
|
38
|
+
} else {
|
|
39
|
+
out[key] = sanitizeValue(entry);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
|
|
14
47
|
/**
|
|
15
48
|
* Logger utility that wraps console methods
|
|
16
49
|
* All console statements should use this logger to avoid eslint warnings
|
|
@@ -21,7 +54,7 @@ const logger = {
|
|
|
21
54
|
* @param {...any} args - Arguments to log
|
|
22
55
|
*/
|
|
23
56
|
log: (...args) => {
|
|
24
|
-
console.log(...args);
|
|
57
|
+
console.log(...args.map((arg) => sanitizeValue(arg)));
|
|
25
58
|
},
|
|
26
59
|
|
|
27
60
|
/**
|
|
@@ -29,7 +62,7 @@ const logger = {
|
|
|
29
62
|
* @param {...any} args - Arguments to log
|
|
30
63
|
*/
|
|
31
64
|
error: (...args) => {
|
|
32
|
-
console.error(...args);
|
|
65
|
+
console.error(...args.map((arg) => sanitizeValue(arg)));
|
|
33
66
|
},
|
|
34
67
|
|
|
35
68
|
/**
|
|
@@ -37,7 +70,7 @@ const logger = {
|
|
|
37
70
|
* @param {...any} args - Arguments to log
|
|
38
71
|
*/
|
|
39
72
|
warn: (...args) => {
|
|
40
|
-
console.warn(...args);
|
|
73
|
+
console.warn(...args.map((arg) => sanitizeValue(arg)));
|
|
41
74
|
},
|
|
42
75
|
|
|
43
76
|
/**
|
|
@@ -45,7 +78,7 @@ const logger = {
|
|
|
45
78
|
* @param {...any} args - Arguments to log
|
|
46
79
|
*/
|
|
47
80
|
info: (...args) => {
|
|
48
|
-
console.log(...args);
|
|
81
|
+
console.log(...args.map((arg) => sanitizeValue(arg)));
|
|
49
82
|
}
|
|
50
83
|
};
|
|
51
84
|
|
package/lib/utils/paths.js
CHANGED
|
@@ -537,19 +537,45 @@ function isAppSubdirSync(root, name) {
|
|
|
537
537
|
}
|
|
538
538
|
|
|
539
539
|
/**
|
|
540
|
-
*
|
|
540
|
+
* True when an integration/builder app directory has application config (yaml/json or legacy variables.yaml).
|
|
541
|
+
* Skips helper folders such as fixtures/ and wizard-configs/.
|
|
542
|
+
* @param {string} appDir - Absolute path to candidate app directory
|
|
543
|
+
* @returns {boolean}
|
|
544
|
+
*/
|
|
545
|
+
function hasApplicationConfigSync(appDir) {
|
|
546
|
+
if (!appDir || typeof appDir !== 'string') return false;
|
|
547
|
+
const disk = nodeFs();
|
|
548
|
+
const names = [
|
|
549
|
+
'application.yaml',
|
|
550
|
+
'application.yml',
|
|
551
|
+
'application.json',
|
|
552
|
+
'variables.yaml'
|
|
553
|
+
];
|
|
554
|
+
return names.some((name) => {
|
|
555
|
+
try {
|
|
556
|
+
return disk.existsSync(path.join(appDir, name));
|
|
557
|
+
} catch {
|
|
558
|
+
return false;
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Lists app names (directories) under integration root. Excludes dot-prefixed entries
|
|
565
|
+
* and directories without application config (e.g. fixtures/, wizard-configs/).
|
|
541
566
|
* Returns [] if root does not exist.
|
|
542
567
|
* @returns {string[]} Sorted list of app directory names
|
|
543
568
|
*/
|
|
544
569
|
function listIntegrationAppNames() {
|
|
545
570
|
const disk = nodeFs();
|
|
546
571
|
const names = new Set();
|
|
572
|
+
const withAppConfig = (root) => (name) => hasApplicationConfigSync(path.join(root, name));
|
|
547
573
|
const cwdRoot = getCwdIntegrationRoot();
|
|
548
574
|
if (cwdRoot) {
|
|
549
|
-
addBuilderSubdirNamesToSet(disk, cwdRoot, names,
|
|
575
|
+
addBuilderSubdirNamesToSet(disk, cwdRoot, names, withAppConfig(cwdRoot));
|
|
550
576
|
}
|
|
551
577
|
const matInt = getIntegrationRoot();
|
|
552
|
-
addBuilderSubdirNamesToSet(disk, matInt, names,
|
|
578
|
+
addBuilderSubdirNamesToSet(disk, matInt, names, withAppConfig(matInt));
|
|
553
579
|
return [...names].sort();
|
|
554
580
|
}
|
|
555
581
|
|
|
@@ -595,7 +621,9 @@ function listBuilderAppNames() {
|
|
|
595
621
|
}
|
|
596
622
|
roots.add(path.resolve(getSystemBuilderRoot()));
|
|
597
623
|
for (const r of roots) {
|
|
598
|
-
addBuilderSubdirNamesToSet(disk, r, names,
|
|
624
|
+
addBuilderSubdirNamesToSet(disk, r, names, (name) =>
|
|
625
|
+
hasApplicationConfigSync(path.join(r, name))
|
|
626
|
+
);
|
|
599
627
|
}
|
|
600
628
|
return [...names].sort();
|
|
601
629
|
}
|
|
@@ -64,6 +64,10 @@ async function uploadApplicationViaPipelineWithAuthRetry(dataplaneUrl, authConfi
|
|
|
64
64
|
if (!isPipelineAuthFailure(first) || authConfig.type !== 'bearer') {
|
|
65
65
|
return first;
|
|
66
66
|
}
|
|
67
|
+
// Token-forward: do not swap the caller JWT for application client-token.
|
|
68
|
+
if (authConfig.preserveBearer) {
|
|
69
|
+
return first;
|
|
70
|
+
}
|
|
67
71
|
const clientAuth = await resolveClientTokenAuthForPipelineRetry(systemKey);
|
|
68
72
|
if (!clientAuth || clientAuth.type === 'bearer') {
|
|
69
73
|
return first;
|
|
@@ -68,29 +68,37 @@ function extractPasswordFromUrlOrValue(urlOrPassword) {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
71
|
+
* Run psql -c inside the Postgres container without a shell.
|
|
72
|
+
* Passes SQL as an argv element so dollar-quoting (DO $$ … $$) is not mangled.
|
|
73
73
|
*
|
|
74
|
-
* @param {string} s
|
|
75
|
-
* @returns {string}
|
|
76
|
-
*/
|
|
77
|
-
function escapeShellDoubleQuoted(s) {
|
|
78
|
-
return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\$/g, '\\$');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
74
|
* @async
|
|
83
75
|
* @param {string} container
|
|
84
76
|
* @param {string} sql
|
|
85
77
|
* @param {string} adminPassword
|
|
78
|
+
* @param {string} [database='postgres']
|
|
86
79
|
* @returns {Promise<void>}
|
|
87
80
|
*/
|
|
88
81
|
async function runPsqlCommand(container, sql, adminPassword, database = 'postgres') {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
82
|
+
await dockerExec.execFileWithDockerEnv(
|
|
83
|
+
'docker',
|
|
84
|
+
[
|
|
85
|
+
'exec',
|
|
86
|
+
'-e',
|
|
87
|
+
'PGPASSWORD',
|
|
88
|
+
'-i',
|
|
89
|
+
container,
|
|
90
|
+
'psql',
|
|
91
|
+
'-U',
|
|
92
|
+
SUPERUSER_ROLE,
|
|
93
|
+
'-d',
|
|
94
|
+
database,
|
|
95
|
+
'-v',
|
|
96
|
+
'ON_ERROR_STOP=1',
|
|
97
|
+
'-c',
|
|
98
|
+
sql
|
|
99
|
+
],
|
|
100
|
+
{ env: { PGPASSWORD: adminPassword } }
|
|
101
|
+
);
|
|
94
102
|
}
|
|
95
103
|
|
|
96
104
|
/**
|
|
@@ -194,6 +202,5 @@ module.exports = {
|
|
|
194
202
|
PLATFORM_DATABASE_SLOTS,
|
|
195
203
|
bootstrapPlatformPostgresDatabases,
|
|
196
204
|
pgRoleName,
|
|
197
|
-
extractPasswordFromUrlOrValue
|
|
198
|
-
escapeShellDoubleQuoted
|
|
205
|
+
extractPasswordFromUrlOrValue
|
|
199
206
|
};
|
|
@@ -86,9 +86,12 @@ async function runPsql(container, sql, adminPassword) {
|
|
|
86
86
|
if (!adminPassword || typeof adminPassword !== 'string') {
|
|
87
87
|
throw new Error('Admin password is required');
|
|
88
88
|
}
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
if (!/^[a-z0-9][a-z0-9._-]*$/i.test(container)) {
|
|
90
|
+
throw new Error(`Unsafe Postgres container name: ${container}`);
|
|
91
|
+
}
|
|
91
92
|
const env = { PGPASSWORD: adminPassword };
|
|
93
|
+
const safeSqlToken = JSON.stringify(String(sql));
|
|
94
|
+
const cmd = `docker exec -e PGPASSWORD -i ${container} psql -U ${SUPERUSER_ROLE} -d postgres -tAc ${safeSqlToken}`;
|
|
92
95
|
const result = (await dockerExec.execWithDockerEnv(cmd, { env })) || {};
|
|
93
96
|
return String(result.stdout || '').trim();
|
|
94
97
|
}
|