@kbediako/codex-orchestrator 0.1.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/LICENSE +7 -0
- package/README.md +238 -0
- package/dist/bin/codex-orchestrator.js +507 -0
- package/dist/orchestrator/src/agents/builder.js +16 -0
- package/dist/orchestrator/src/agents/index.js +4 -0
- package/dist/orchestrator/src/agents/planner.js +17 -0
- package/dist/orchestrator/src/agents/reviewer.js +13 -0
- package/dist/orchestrator/src/agents/tester.js +13 -0
- package/dist/orchestrator/src/cli/adapters/CommandBuilder.js +20 -0
- package/dist/orchestrator/src/cli/adapters/CommandPlanner.js +164 -0
- package/dist/orchestrator/src/cli/adapters/CommandReviewer.js +32 -0
- package/dist/orchestrator/src/cli/adapters/CommandTester.js +33 -0
- package/dist/orchestrator/src/cli/adapters/index.js +4 -0
- package/dist/orchestrator/src/cli/config/userConfig.js +28 -0
- package/dist/orchestrator/src/cli/doctor.js +48 -0
- package/dist/orchestrator/src/cli/events/runEvents.js +84 -0
- package/dist/orchestrator/src/cli/exec/command.js +56 -0
- package/dist/orchestrator/src/cli/exec/context.js +108 -0
- package/dist/orchestrator/src/cli/exec/experience.js +77 -0
- package/dist/orchestrator/src/cli/exec/finalization.js +140 -0
- package/dist/orchestrator/src/cli/exec/learning.js +62 -0
- package/dist/orchestrator/src/cli/exec/stageRunner.js +71 -0
- package/dist/orchestrator/src/cli/exec/summary.js +109 -0
- package/dist/orchestrator/src/cli/exec/telemetry.js +18 -0
- package/dist/orchestrator/src/cli/exec/tfgrpo.js +200 -0
- package/dist/orchestrator/src/cli/exec/tfgrpoArtifacts.js +19 -0
- package/dist/orchestrator/src/cli/exec/types.js +1 -0
- package/dist/orchestrator/src/cli/init.js +64 -0
- package/dist/orchestrator/src/cli/mcp.js +124 -0
- package/dist/orchestrator/src/cli/metrics/metricsAggregator.js +404 -0
- package/dist/orchestrator/src/cli/metrics/metricsRecorder.js +138 -0
- package/dist/orchestrator/src/cli/orchestrator.js +554 -0
- package/dist/orchestrator/src/cli/pipelines/defaultDiagnostics.js +32 -0
- package/dist/orchestrator/src/cli/pipelines/designReference.js +72 -0
- package/dist/orchestrator/src/cli/pipelines/hiFiDesignToolkit.js +71 -0
- package/dist/orchestrator/src/cli/pipelines/index.js +34 -0
- package/dist/orchestrator/src/cli/run/environment.js +24 -0
- package/dist/orchestrator/src/cli/run/manifest.js +367 -0
- package/dist/orchestrator/src/cli/run/manifestPersister.js +88 -0
- package/dist/orchestrator/src/cli/run/runPaths.js +30 -0
- package/dist/orchestrator/src/cli/selfCheck.js +12 -0
- package/dist/orchestrator/src/cli/services/commandRunner.js +420 -0
- package/dist/orchestrator/src/cli/services/controlPlaneService.js +107 -0
- package/dist/orchestrator/src/cli/services/execRuntime.js +69 -0
- package/dist/orchestrator/src/cli/services/pipelineResolver.js +47 -0
- package/dist/orchestrator/src/cli/services/runPreparation.js +82 -0
- package/dist/orchestrator/src/cli/services/runSummaryWriter.js +35 -0
- package/dist/orchestrator/src/cli/services/schedulerService.js +42 -0
- package/dist/orchestrator/src/cli/tasks/taskMetadata.js +19 -0
- package/dist/orchestrator/src/cli/telemetry/schema.js +8 -0
- package/dist/orchestrator/src/cli/types.js +1 -0
- package/dist/orchestrator/src/cli/ui/HudApp.js +112 -0
- package/dist/orchestrator/src/cli/ui/controller.js +26 -0
- package/dist/orchestrator/src/cli/ui/store.js +240 -0
- package/dist/orchestrator/src/cli/utils/enforcementMode.js +12 -0
- package/dist/orchestrator/src/cli/utils/fs.js +8 -0
- package/dist/orchestrator/src/cli/utils/interactive.js +25 -0
- package/dist/orchestrator/src/cli/utils/jsonlWriter.js +10 -0
- package/dist/orchestrator/src/cli/utils/optionalDeps.js +30 -0
- package/dist/orchestrator/src/cli/utils/packageInfo.js +25 -0
- package/dist/orchestrator/src/cli/utils/planFormatter.js +49 -0
- package/dist/orchestrator/src/cli/utils/runId.js +7 -0
- package/dist/orchestrator/src/cli/utils/specGuardRunner.js +26 -0
- package/dist/orchestrator/src/cli/utils/strings.js +8 -0
- package/dist/orchestrator/src/cli/utils/time.js +6 -0
- package/dist/orchestrator/src/control-plane/drift-reporter.js +109 -0
- package/dist/orchestrator/src/control-plane/index.js +3 -0
- package/dist/orchestrator/src/control-plane/request-builder.js +217 -0
- package/dist/orchestrator/src/control-plane/types.js +1 -0
- package/dist/orchestrator/src/control-plane/validator.js +50 -0
- package/dist/orchestrator/src/credentials/CredentialBroker.js +1 -0
- package/dist/orchestrator/src/events/EventBus.js +25 -0
- package/dist/orchestrator/src/learning/crystalizer.js +108 -0
- package/dist/orchestrator/src/learning/harvester.js +146 -0
- package/dist/orchestrator/src/learning/manifest.js +56 -0
- package/dist/orchestrator/src/learning/runner.js +177 -0
- package/dist/orchestrator/src/learning/validator.js +164 -0
- package/dist/orchestrator/src/logger.js +20 -0
- package/dist/orchestrator/src/manager.js +388 -0
- package/dist/orchestrator/src/persistence/ArtifactStager.js +95 -0
- package/dist/orchestrator/src/persistence/ExperienceStore.js +210 -0
- package/dist/orchestrator/src/persistence/PersistenceCoordinator.js +65 -0
- package/dist/orchestrator/src/persistence/RunManifestWriter.js +23 -0
- package/dist/orchestrator/src/persistence/TaskStateStore.js +172 -0
- package/dist/orchestrator/src/persistence/identifierGuards.js +1 -0
- package/dist/orchestrator/src/persistence/lockFile.js +26 -0
- package/dist/orchestrator/src/persistence/sanitizeIdentifier.js +26 -0
- package/dist/orchestrator/src/persistence/sanitizeRunId.js +8 -0
- package/dist/orchestrator/src/persistence/sanitizeTaskId.js +8 -0
- package/dist/orchestrator/src/persistence/writeAtomicFile.js +4 -0
- package/dist/orchestrator/src/privacy/guard.js +111 -0
- package/dist/orchestrator/src/scheduler/index.js +1 -0
- package/dist/orchestrator/src/scheduler/plan.js +171 -0
- package/dist/orchestrator/src/scheduler/types.js +1 -0
- package/dist/orchestrator/src/sync/CloudRunsClient.js +1 -0
- package/dist/orchestrator/src/sync/CloudRunsHttpClient.js +82 -0
- package/dist/orchestrator/src/sync/CloudSyncWorker.js +206 -0
- package/dist/orchestrator/src/sync/createCloudSyncWorker.js +15 -0
- package/dist/orchestrator/src/types.js +1 -0
- package/dist/orchestrator/src/utils/atomicWrite.js +15 -0
- package/dist/orchestrator/src/utils/errorMessage.js +14 -0
- package/dist/orchestrator/src/utils/executionMode.js +69 -0
- package/dist/packages/control-plane-schemas/src/index.js +1 -0
- package/dist/packages/control-plane-schemas/src/run-request.js +548 -0
- package/dist/packages/orchestrator/src/exec/handle-service.js +203 -0
- package/dist/packages/orchestrator/src/exec/session-manager.js +147 -0
- package/dist/packages/orchestrator/src/exec/unified-exec.js +432 -0
- package/dist/packages/orchestrator/src/index.js +3 -0
- package/dist/packages/orchestrator/src/instructions/loader.js +101 -0
- package/dist/packages/orchestrator/src/instructions/promptPacks.js +151 -0
- package/dist/packages/orchestrator/src/notifications/index.js +74 -0
- package/dist/packages/orchestrator/src/telemetry/otel-exporter.js +142 -0
- package/dist/packages/orchestrator/src/tool-orchestrator.js +161 -0
- package/dist/packages/sdk-node/src/orchestrator.js +195 -0
- package/dist/packages/shared/config/designConfig.js +495 -0
- package/dist/packages/shared/config/env.js +37 -0
- package/dist/packages/shared/config/index.js +2 -0
- package/dist/packages/shared/design-artifacts/writer.js +221 -0
- package/dist/packages/shared/events/serializer.js +84 -0
- package/dist/packages/shared/events/types.js +1 -0
- package/dist/packages/shared/manifest/artifactUtils.js +36 -0
- package/dist/packages/shared/manifest/designArtifacts.js +665 -0
- package/dist/packages/shared/manifest/fileIO.js +29 -0
- package/dist/packages/shared/manifest/toolRuns.js +78 -0
- package/dist/packages/shared/manifest/toolkitArtifacts.js +223 -0
- package/dist/packages/shared/manifest/types.js +5 -0
- package/dist/packages/shared/manifest/validator.js +73 -0
- package/dist/packages/shared/manifest/writer.js +2 -0
- package/dist/packages/shared/streams/stdio.js +112 -0
- package/dist/scripts/design/pipeline/advanced-assets.js +466 -0
- package/dist/scripts/design/pipeline/componentize.js +74 -0
- package/dist/scripts/design/pipeline/context.js +34 -0
- package/dist/scripts/design/pipeline/extract.js +249 -0
- package/dist/scripts/design/pipeline/optionalDeps.js +107 -0
- package/dist/scripts/design/pipeline/prepare.js +46 -0
- package/dist/scripts/design/pipeline/reference.js +94 -0
- package/dist/scripts/design/pipeline/state.js +206 -0
- package/dist/scripts/design/pipeline/toolkit/common.js +94 -0
- package/dist/scripts/design/pipeline/toolkit/extract.js +258 -0
- package/dist/scripts/design/pipeline/toolkit/publish.js +202 -0
- package/dist/scripts/design/pipeline/toolkit/publishActions.js +12 -0
- package/dist/scripts/design/pipeline/toolkit/reference.js +846 -0
- package/dist/scripts/design/pipeline/toolkit/snapshot.js +882 -0
- package/dist/scripts/design/pipeline/toolkit/tokens.js +456 -0
- package/dist/scripts/design/pipeline/visual-regression.js +137 -0
- package/dist/scripts/design/pipeline/write-artifacts.js +61 -0
- package/package.json +97 -0
- package/schemas/manifest.json +1064 -0
- package/templates/README.md +12 -0
- package/templates/codex/mcp-client.json +8 -0
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { load as parseYaml } from 'js-yaml';
|
|
4
|
+
const DEFAULT_CONFIG_PATH = 'design.config.yaml';
|
|
5
|
+
const DEFAULT_RETENTION_DAYS = 30;
|
|
6
|
+
const DEFAULT_CONFIG = {
|
|
7
|
+
metadata: {
|
|
8
|
+
design: {
|
|
9
|
+
enabled: false,
|
|
10
|
+
captureUrls: [],
|
|
11
|
+
breakpoints: [],
|
|
12
|
+
maskSelectors: [],
|
|
13
|
+
retention: {
|
|
14
|
+
days: DEFAULT_RETENTION_DAYS,
|
|
15
|
+
autoPurge: false
|
|
16
|
+
},
|
|
17
|
+
privacy: {
|
|
18
|
+
allowThirdParty: false,
|
|
19
|
+
requireApproval: true,
|
|
20
|
+
approver: null
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
advanced: {
|
|
25
|
+
framerMotion: {
|
|
26
|
+
enabled: false,
|
|
27
|
+
quotaSeconds: 0,
|
|
28
|
+
approver: null
|
|
29
|
+
},
|
|
30
|
+
ffmpeg: {
|
|
31
|
+
enabled: false,
|
|
32
|
+
quotaSeconds: 0,
|
|
33
|
+
approver: null,
|
|
34
|
+
maxDurationSeconds: null
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
pipelines: {
|
|
38
|
+
designReference: {
|
|
39
|
+
continueOnFailure: false,
|
|
40
|
+
visualRegression: {
|
|
41
|
+
enabled: true,
|
|
42
|
+
baselineDir: null
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
hiFiDesignToolkit: {
|
|
46
|
+
enabled: false,
|
|
47
|
+
sources: [],
|
|
48
|
+
breakpoints: [],
|
|
49
|
+
maskSelectors: [],
|
|
50
|
+
retention: null,
|
|
51
|
+
selfCorrection: {
|
|
52
|
+
enabled: false,
|
|
53
|
+
maxIterations: 1,
|
|
54
|
+
provider: null,
|
|
55
|
+
approvalId: null,
|
|
56
|
+
threshold: null
|
|
57
|
+
},
|
|
58
|
+
publish: {
|
|
59
|
+
updateTokens: true,
|
|
60
|
+
updateComponents: true,
|
|
61
|
+
runVisualRegression: true
|
|
62
|
+
},
|
|
63
|
+
liveAssets: {
|
|
64
|
+
keepScripts: false,
|
|
65
|
+
allowRemoteAssets: false,
|
|
66
|
+
maxStylesheets: null,
|
|
67
|
+
mirrorAssets: false
|
|
68
|
+
},
|
|
69
|
+
interactions: {
|
|
70
|
+
enabled: false,
|
|
71
|
+
scriptPath: null,
|
|
72
|
+
waitMs: null
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const DESIGN_REFERENCE_PIPELINE_ID = 'design-reference';
|
|
78
|
+
const HI_FI_TOOLKIT_PIPELINE_ID = 'hi-fi-design-toolkit';
|
|
79
|
+
export function designPipelineId(result, env = process.env) {
|
|
80
|
+
return selectDesignPipeline(result ?? null, env).id;
|
|
81
|
+
}
|
|
82
|
+
export async function loadDesignConfig(options = {}) {
|
|
83
|
+
const rootDir = options.rootDir ?? process.cwd();
|
|
84
|
+
const path = options.filePath ?? join(rootDir, DEFAULT_CONFIG_PATH);
|
|
85
|
+
try {
|
|
86
|
+
const raw = await readFile(path, 'utf-8');
|
|
87
|
+
const parsed = parseYaml(raw);
|
|
88
|
+
const warnings = [];
|
|
89
|
+
const config = normalizeDesignConfig(parsed, warnings);
|
|
90
|
+
return { config, path, exists: true, warnings };
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
const nodeError = error;
|
|
94
|
+
if (nodeError && nodeError.code === 'ENOENT') {
|
|
95
|
+
return { config: structuredClone(DEFAULT_CONFIG), path, exists: false, warnings: [] };
|
|
96
|
+
}
|
|
97
|
+
throw new Error(`Failed to load design config at ${path}: ${error.message}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export function shouldActivateDesignPipeline(result, env = process.env) {
|
|
101
|
+
return selectDesignPipeline(result, env).shouldRun;
|
|
102
|
+
}
|
|
103
|
+
function normalizeDesignConfig(raw, warnings) {
|
|
104
|
+
const base = structuredClone(DEFAULT_CONFIG);
|
|
105
|
+
if (!raw || typeof raw !== 'object') {
|
|
106
|
+
return base;
|
|
107
|
+
}
|
|
108
|
+
const metadata = raw.metadata;
|
|
109
|
+
if (metadata && typeof metadata === 'object') {
|
|
110
|
+
const design = metadata.design;
|
|
111
|
+
if (design && typeof design === 'object') {
|
|
112
|
+
base.metadata.design.enabled = coerceBoolean(design.enabled, base.metadata.design.enabled);
|
|
113
|
+
base.metadata.design.captureUrls = normalizeStringArray(design.capture_urls, warnings, 'metadata.design.capture_urls');
|
|
114
|
+
base.metadata.design.breakpoints = normalizeBreakpoints(design.breakpoints, warnings);
|
|
115
|
+
base.metadata.design.maskSelectors = normalizeStringArray(design.mask_selectors, warnings, 'metadata.design.mask_selectors');
|
|
116
|
+
const retentionRaw = design.retention;
|
|
117
|
+
if (retentionRaw && typeof retentionRaw === 'object') {
|
|
118
|
+
const retentionRecord = retentionRaw;
|
|
119
|
+
const retentionDays = coerceNumber(retentionRecord.days, base.metadata.design.retention.days, {
|
|
120
|
+
min: 1,
|
|
121
|
+
field: 'metadata.design.retention.days',
|
|
122
|
+
warnings
|
|
123
|
+
}) ?? base.metadata.design.retention.days;
|
|
124
|
+
base.metadata.design.retention.days = retentionDays;
|
|
125
|
+
base.metadata.design.retention.autoPurge = coerceBoolean(retentionRecord.auto_purge, base.metadata.design.retention.autoPurge);
|
|
126
|
+
}
|
|
127
|
+
const privacyRaw = design.privacy;
|
|
128
|
+
if (privacyRaw && typeof privacyRaw === 'object') {
|
|
129
|
+
const privacyRecord = privacyRaw;
|
|
130
|
+
base.metadata.design.privacy.allowThirdParty = coerceBoolean(privacyRecord.allow_third_party, base.metadata.design.privacy.allowThirdParty);
|
|
131
|
+
base.metadata.design.privacy.requireApproval = coerceBoolean(privacyRecord.require_approval, base.metadata.design.privacy.requireApproval);
|
|
132
|
+
const approver = coerceString(privacyRecord.approver);
|
|
133
|
+
base.metadata.design.privacy.approver = approver ?? base.metadata.design.privacy.approver ?? null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const advanced = raw.advanced;
|
|
138
|
+
if (advanced && typeof advanced === 'object') {
|
|
139
|
+
const advancedRecord = advanced;
|
|
140
|
+
base.advanced.framerMotion = normalizeAdvancedConfig(advancedRecord.framer_motion, base.advanced.framerMotion, 'advanced.framer_motion', warnings);
|
|
141
|
+
const ffmpeg = normalizeAdvancedConfig(advancedRecord.ffmpeg, base.advanced.ffmpeg, 'advanced.ffmpeg', warnings);
|
|
142
|
+
const ffmpegRecord = advancedRecord.ffmpeg && typeof advancedRecord.ffmpeg === 'object'
|
|
143
|
+
? advancedRecord.ffmpeg
|
|
144
|
+
: null;
|
|
145
|
+
const ffmpegExtended = {
|
|
146
|
+
...ffmpeg,
|
|
147
|
+
maxDurationSeconds: base.advanced.ffmpeg.maxDurationSeconds ?? null
|
|
148
|
+
};
|
|
149
|
+
if (ffmpegRecord) {
|
|
150
|
+
const duration = coerceNumber(ffmpegRecord.max_duration_seconds, null, {
|
|
151
|
+
min: 1,
|
|
152
|
+
allowNull: true,
|
|
153
|
+
field: 'advanced.ffmpeg.max_duration_seconds',
|
|
154
|
+
warnings
|
|
155
|
+
});
|
|
156
|
+
ffmpegExtended.maxDurationSeconds = duration;
|
|
157
|
+
}
|
|
158
|
+
base.advanced.ffmpeg = ffmpegExtended;
|
|
159
|
+
}
|
|
160
|
+
const pipelines = raw.pipelines;
|
|
161
|
+
if (pipelines && typeof pipelines === 'object') {
|
|
162
|
+
const designReference = pipelines['design-reference'];
|
|
163
|
+
if (designReference && typeof designReference === 'object') {
|
|
164
|
+
const record = designReference;
|
|
165
|
+
const continueOnFailure = record.continue_on_failure;
|
|
166
|
+
if (continueOnFailure !== undefined) {
|
|
167
|
+
base.pipelines.designReference.continueOnFailure = coerceBoolean(continueOnFailure, Boolean(base.pipelines.designReference.continueOnFailure));
|
|
168
|
+
}
|
|
169
|
+
const visualRegression = record.visual_regression;
|
|
170
|
+
if (visualRegression && typeof visualRegression === 'object') {
|
|
171
|
+
const vrRecord = visualRegression;
|
|
172
|
+
base.pipelines.designReference.visualRegression = {
|
|
173
|
+
enabled: coerceBoolean(vrRecord.enabled, base.pipelines.designReference.visualRegression?.enabled ?? true),
|
|
174
|
+
baselineDir: coerceString(vrRecord.baseline_dir) ?? null
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const hiFi = pipelines['hi_fi_design_toolkit'];
|
|
179
|
+
if (hiFi && typeof hiFi === 'object') {
|
|
180
|
+
base.pipelines.hiFiDesignToolkit = normalizeToolkitPipelineConfig(hiFi, base, warnings);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return base;
|
|
184
|
+
}
|
|
185
|
+
function selectDesignPipeline(result, env) {
|
|
186
|
+
const config = result?.config ?? DEFAULT_CONFIG;
|
|
187
|
+
const toolkitEnv = typeof env.DESIGN_TOOLKIT === 'string' ? isTruthyFlag(env.DESIGN_TOOLKIT) : null;
|
|
188
|
+
if (toolkitEnv !== null) {
|
|
189
|
+
return { id: HI_FI_TOOLKIT_PIPELINE_ID, shouldRun: toolkitEnv };
|
|
190
|
+
}
|
|
191
|
+
const designPipelineEnv = typeof env.DESIGN_PIPELINE === 'string' ? isTruthyFlag(env.DESIGN_PIPELINE) : null;
|
|
192
|
+
if (designPipelineEnv !== null) {
|
|
193
|
+
return { id: DESIGN_REFERENCE_PIPELINE_ID, shouldRun: designPipelineEnv };
|
|
194
|
+
}
|
|
195
|
+
const referenceEnv = typeof env.DESIGN_REFERENCE_PIPELINE === 'string' ? isTruthyFlag(env.DESIGN_REFERENCE_PIPELINE) : null;
|
|
196
|
+
if (referenceEnv !== null) {
|
|
197
|
+
return { id: DESIGN_REFERENCE_PIPELINE_ID, shouldRun: referenceEnv };
|
|
198
|
+
}
|
|
199
|
+
const toolkitConfigEnabled = config.pipelines.hiFiDesignToolkit.enabled && config.pipelines.hiFiDesignToolkit.sources.length > 0;
|
|
200
|
+
if (toolkitConfigEnabled) {
|
|
201
|
+
return { id: HI_FI_TOOLKIT_PIPELINE_ID, shouldRun: true };
|
|
202
|
+
}
|
|
203
|
+
if (config.metadata.design.enabled) {
|
|
204
|
+
return { id: DESIGN_REFERENCE_PIPELINE_ID, shouldRun: true };
|
|
205
|
+
}
|
|
206
|
+
return { id: DESIGN_REFERENCE_PIPELINE_ID, shouldRun: false };
|
|
207
|
+
}
|
|
208
|
+
function normalizeBreakpoints(raw, warnings) {
|
|
209
|
+
if (!Array.isArray(raw)) {
|
|
210
|
+
return [];
|
|
211
|
+
}
|
|
212
|
+
const result = [];
|
|
213
|
+
raw.forEach((candidate, index) => {
|
|
214
|
+
if (!candidate || typeof candidate !== 'object') {
|
|
215
|
+
warnings.push(`breakpoints[${index}]: expected object`);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const record = candidate;
|
|
219
|
+
const id = coerceString(record.id) ?? `bp-${index + 1}`;
|
|
220
|
+
const width = coerceNumber(record.width, null, {
|
|
221
|
+
min: 1,
|
|
222
|
+
field: `breakpoints[${index}].width`,
|
|
223
|
+
warnings
|
|
224
|
+
});
|
|
225
|
+
const height = coerceNumber(record.height, null, {
|
|
226
|
+
min: 1,
|
|
227
|
+
field: `breakpoints[${index}].height`,
|
|
228
|
+
warnings
|
|
229
|
+
});
|
|
230
|
+
if (width === null || height === null) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const deviceScaleFactor = coerceNumber(record.deviceScaleFactor ?? record.device_scale_factor, null, {
|
|
234
|
+
min: 0.1,
|
|
235
|
+
allowNull: true,
|
|
236
|
+
field: `breakpoints[${index}].deviceScaleFactor`,
|
|
237
|
+
warnings
|
|
238
|
+
});
|
|
239
|
+
const breakpoint = {
|
|
240
|
+
id,
|
|
241
|
+
width,
|
|
242
|
+
height
|
|
243
|
+
};
|
|
244
|
+
if (deviceScaleFactor !== null) {
|
|
245
|
+
breakpoint.deviceScaleFactor = deviceScaleFactor;
|
|
246
|
+
}
|
|
247
|
+
result.push(breakpoint);
|
|
248
|
+
});
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
function normalizeToolkitPipelineConfig(raw, baseConfig, warnings) {
|
|
252
|
+
const normalized = structuredClone(DEFAULT_CONFIG.pipelines.hiFiDesignToolkit);
|
|
253
|
+
normalized.enabled = coerceBoolean(raw.enabled, normalized.enabled);
|
|
254
|
+
normalized.maskSelectors = normalizeStringArray(raw.mask_selectors ?? raw.maskSelectors, warnings, 'pipelines.hi_fi_design_toolkit.mask_selectors');
|
|
255
|
+
normalized.breakpoints = normalizeBreakpoints(raw.breakpoints, warnings);
|
|
256
|
+
normalized.sources = normalizeToolkitSources(raw.sources, warnings);
|
|
257
|
+
const retentionRaw = raw.retention;
|
|
258
|
+
if (retentionRaw && typeof retentionRaw === 'object') {
|
|
259
|
+
const record = retentionRaw;
|
|
260
|
+
const defaultRetention = baseConfig.metadata.design.retention;
|
|
261
|
+
const days = coerceNumber(record.days, defaultRetention.days, {
|
|
262
|
+
min: 1,
|
|
263
|
+
field: 'pipelines.hi_fi_design_toolkit.retention.days',
|
|
264
|
+
warnings
|
|
265
|
+
}) ?? defaultRetention.days;
|
|
266
|
+
const autoPurge = coerceBoolean(record.auto_purge ?? record.autoPurge, defaultRetention.autoPurge);
|
|
267
|
+
normalized.retention = {
|
|
268
|
+
days,
|
|
269
|
+
autoPurge,
|
|
270
|
+
policy: coerceString(record.policy) ?? 'design.config.retention'
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
const selfCorrectionRaw = raw.self_correction ?? raw.selfCorrection;
|
|
274
|
+
if (selfCorrectionRaw && typeof selfCorrectionRaw === 'object') {
|
|
275
|
+
const record = selfCorrectionRaw;
|
|
276
|
+
normalized.selfCorrection = {
|
|
277
|
+
enabled: coerceBoolean(record.enabled, normalized.selfCorrection.enabled),
|
|
278
|
+
maxIterations: coerceNumber(record.max_iterations ?? record.maxIterations, normalized.selfCorrection.maxIterations, {
|
|
279
|
+
min: 1,
|
|
280
|
+
field: 'pipelines.hi_fi_design_toolkit.self_correction.max_iterations',
|
|
281
|
+
warnings
|
|
282
|
+
}) ?? normalized.selfCorrection.maxIterations,
|
|
283
|
+
provider: coerceString(record.provider) ?? normalized.selfCorrection.provider ?? null,
|
|
284
|
+
approvalId: coerceString(record.approval_id ?? record.approvalId) ?? normalized.selfCorrection.approvalId ?? null,
|
|
285
|
+
threshold: coerceNumber(record.threshold, normalized.selfCorrection.threshold ?? null, {
|
|
286
|
+
min: 0,
|
|
287
|
+
allowNull: true,
|
|
288
|
+
field: 'pipelines.hi_fi_design_toolkit.self_correction.threshold',
|
|
289
|
+
warnings
|
|
290
|
+
}) ?? normalized.selfCorrection.threshold ?? null
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
const publishRaw = raw.publish;
|
|
294
|
+
if (publishRaw && typeof publishRaw === 'object') {
|
|
295
|
+
const record = publishRaw;
|
|
296
|
+
normalized.publish = {
|
|
297
|
+
updateTokens: coerceBoolean(record.update_tokens ?? record.updateTokens ?? record.sync_tokens, normalized.publish.updateTokens),
|
|
298
|
+
updateComponents: coerceBoolean(record.update_components ?? record.updateComponents ?? record.componentize, normalized.publish.updateComponents),
|
|
299
|
+
runVisualRegression: coerceBoolean(record.run_visual_regression ?? record.visual_regression, normalized.publish.runVisualRegression)
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
const liveAssetsRaw = raw.live_assets ?? raw.liveAssets;
|
|
303
|
+
if (liveAssetsRaw && typeof liveAssetsRaw === 'object') {
|
|
304
|
+
const record = liveAssetsRaw;
|
|
305
|
+
normalized.liveAssets = {
|
|
306
|
+
keepScripts: coerceBoolean(record.keep_scripts ?? record.keepScripts, normalized.liveAssets.keepScripts),
|
|
307
|
+
allowRemoteAssets: coerceBoolean(record.allow_remote_assets ?? record.allowRemoteAssets, normalized.liveAssets.allowRemoteAssets),
|
|
308
|
+
maxStylesheets: coerceNumber(record.max_stylesheets ?? record.maxStylesheets, normalized.liveAssets.maxStylesheets ?? null, {
|
|
309
|
+
min: 1,
|
|
310
|
+
allowNull: true,
|
|
311
|
+
field: 'pipelines.hi_fi_design_toolkit.live_assets.max_stylesheets',
|
|
312
|
+
warnings
|
|
313
|
+
}),
|
|
314
|
+
mirrorAssets: coerceBoolean(record.mirror_assets ?? record.mirrorAssets, normalized.liveAssets.mirrorAssets)
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
const interactionsRaw = raw.interactions;
|
|
318
|
+
if (interactionsRaw && typeof interactionsRaw === 'object') {
|
|
319
|
+
const record = interactionsRaw;
|
|
320
|
+
normalized.interactions = {
|
|
321
|
+
enabled: coerceBoolean(record.enabled, normalized.interactions.enabled),
|
|
322
|
+
scriptPath: coerceString(record.script_path ?? record.scriptPath) ?? normalized.interactions.scriptPath ?? null,
|
|
323
|
+
waitMs: coerceNumber(record.wait_ms ?? record.waitMs, normalized.interactions.waitMs ?? null, {
|
|
324
|
+
min: 0,
|
|
325
|
+
allowNull: true,
|
|
326
|
+
field: 'pipelines.hi_fi_design_toolkit.interactions.wait_ms',
|
|
327
|
+
warnings
|
|
328
|
+
}) ?? normalized.interactions.waitMs ?? null
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
return normalized;
|
|
332
|
+
}
|
|
333
|
+
function normalizeToolkitSources(raw, warnings) {
|
|
334
|
+
if (!raw) {
|
|
335
|
+
return [];
|
|
336
|
+
}
|
|
337
|
+
if (!Array.isArray(raw)) {
|
|
338
|
+
warnings.push('pipelines.hi_fi_design_toolkit.sources: expected array');
|
|
339
|
+
return [];
|
|
340
|
+
}
|
|
341
|
+
const result = [];
|
|
342
|
+
raw.forEach((entry, index) => {
|
|
343
|
+
if (typeof entry === 'string') {
|
|
344
|
+
const url = entry.trim();
|
|
345
|
+
if (!url) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
const slug = sanitizeToolkitSlug(url, index);
|
|
349
|
+
result.push({
|
|
350
|
+
id: `source-${index + 1}`,
|
|
351
|
+
url,
|
|
352
|
+
referenceUrl: url,
|
|
353
|
+
slug,
|
|
354
|
+
title: null
|
|
355
|
+
});
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (!entry || typeof entry !== 'object') {
|
|
359
|
+
warnings.push(`pipelines.hi_fi_design_toolkit.sources[${index}]: expected string or object`);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const record = entry;
|
|
363
|
+
const url = coerceString(record.url ?? record.capture_url ?? record.source);
|
|
364
|
+
if (!url) {
|
|
365
|
+
warnings.push(`pipelines.hi_fi_design_toolkit.sources[${index}]: missing url`);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const identifier = coerceString(record.id ?? record.slug ?? record.name) ?? `source-${index + 1}`;
|
|
369
|
+
const slug = sanitizeToolkitSlug(coerceString(record.slug) ?? identifier, index);
|
|
370
|
+
const referenceUrl = coerceString(record.reference_url ?? record.referenceUrl) ?? url;
|
|
371
|
+
const title = coerceString(record.title);
|
|
372
|
+
result.push({
|
|
373
|
+
id: identifier,
|
|
374
|
+
url,
|
|
375
|
+
referenceUrl,
|
|
376
|
+
slug,
|
|
377
|
+
title
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
return result;
|
|
381
|
+
}
|
|
382
|
+
function sanitizeToolkitSlug(candidate, index) {
|
|
383
|
+
const normalized = candidate
|
|
384
|
+
.toLowerCase()
|
|
385
|
+
.trim()
|
|
386
|
+
.replace(/[^a-z0-9-_]+/g, '-')
|
|
387
|
+
.replace(/^-+|-+$/g, '')
|
|
388
|
+
.slice(0, 48);
|
|
389
|
+
if (normalized.length > 0) {
|
|
390
|
+
return normalized;
|
|
391
|
+
}
|
|
392
|
+
return `source-${index + 1}`;
|
|
393
|
+
}
|
|
394
|
+
function normalizeAdvancedConfig(raw, defaults, field, warnings) {
|
|
395
|
+
if (!raw || typeof raw !== 'object') {
|
|
396
|
+
return { ...defaults };
|
|
397
|
+
}
|
|
398
|
+
const record = raw;
|
|
399
|
+
return {
|
|
400
|
+
enabled: coerceBoolean(record.enabled, defaults.enabled),
|
|
401
|
+
quotaSeconds: coerceNumber(record.quota_seconds ?? record.quotaSeconds, defaults.quotaSeconds, {
|
|
402
|
+
min: 0,
|
|
403
|
+
field: `${field}.quota_seconds`,
|
|
404
|
+
warnings
|
|
405
|
+
}) ?? defaults.quotaSeconds,
|
|
406
|
+
approver: coerceString(record.approver) ?? defaults.approver ?? null
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
function normalizeStringArray(raw, warnings, field) {
|
|
410
|
+
if (!raw) {
|
|
411
|
+
return [];
|
|
412
|
+
}
|
|
413
|
+
if (!Array.isArray(raw)) {
|
|
414
|
+
warnings.push(`${field}: expected array`);
|
|
415
|
+
return [];
|
|
416
|
+
}
|
|
417
|
+
return raw
|
|
418
|
+
.map((value, index) => {
|
|
419
|
+
const text = coerceString(value);
|
|
420
|
+
if (!text) {
|
|
421
|
+
warnings.push(`${field}[${index}]: ignored non-string entry`);
|
|
422
|
+
}
|
|
423
|
+
return text;
|
|
424
|
+
})
|
|
425
|
+
.filter((value) => Boolean(value))
|
|
426
|
+
.map((value) => value.trim())
|
|
427
|
+
.filter((value) => value.length > 0);
|
|
428
|
+
}
|
|
429
|
+
function coerceNumber(value, fallback, options = {}) {
|
|
430
|
+
if (value === null || value === undefined || value === '') {
|
|
431
|
+
if (!options.allowNull && options.field && options.warnings) {
|
|
432
|
+
options.warnings.push(`${options.field}: missing value`);
|
|
433
|
+
}
|
|
434
|
+
return options.allowNull ? null : fallback ?? null;
|
|
435
|
+
}
|
|
436
|
+
const numeric = typeof value === 'number' ? value : Number(value);
|
|
437
|
+
if (!Number.isFinite(numeric)) {
|
|
438
|
+
if (options.warnings && options.field) {
|
|
439
|
+
options.warnings.push(`${options.field}: expected numeric value`);
|
|
440
|
+
}
|
|
441
|
+
return options.allowNull ? null : fallback ?? null;
|
|
442
|
+
}
|
|
443
|
+
if (options.min !== undefined && numeric < options.min) {
|
|
444
|
+
if (options.warnings && options.field) {
|
|
445
|
+
options.warnings.push(`${options.field}: clamped to minimum ${options.min} (received ${numeric})`);
|
|
446
|
+
}
|
|
447
|
+
return options.min;
|
|
448
|
+
}
|
|
449
|
+
if (options.max !== undefined && numeric > options.max) {
|
|
450
|
+
if (options.warnings && options.field) {
|
|
451
|
+
options.warnings.push(`${options.field}: clamped to maximum ${options.max} (received ${numeric})`);
|
|
452
|
+
}
|
|
453
|
+
return options.max;
|
|
454
|
+
}
|
|
455
|
+
return numeric;
|
|
456
|
+
}
|
|
457
|
+
function coerceBoolean(value, fallback) {
|
|
458
|
+
if (typeof value === 'boolean') {
|
|
459
|
+
return value;
|
|
460
|
+
}
|
|
461
|
+
if (typeof value === 'number') {
|
|
462
|
+
return value !== 0;
|
|
463
|
+
}
|
|
464
|
+
if (typeof value === 'string') {
|
|
465
|
+
return isTruthyFlag(value) ?? fallback;
|
|
466
|
+
}
|
|
467
|
+
return fallback;
|
|
468
|
+
}
|
|
469
|
+
function coerceString(value) {
|
|
470
|
+
if (typeof value === 'string') {
|
|
471
|
+
const trimmed = value.trim();
|
|
472
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
473
|
+
}
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
function isTruthyFlag(value) {
|
|
477
|
+
const normalized = value.trim().toLowerCase();
|
|
478
|
+
if (!normalized) {
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
if (['1', 'true', 'yes', 'on', 'enabled'].includes(normalized)) {
|
|
482
|
+
return true;
|
|
483
|
+
}
|
|
484
|
+
if (['0', 'false', 'no', 'off', 'disabled'].includes(normalized)) {
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
// structuredClone fallback for Node versions that may lack global support.
|
|
490
|
+
function structuredClone(value) {
|
|
491
|
+
if (typeof globalThis.structuredClone === 'function') {
|
|
492
|
+
return globalThis.structuredClone(value);
|
|
493
|
+
}
|
|
494
|
+
return JSON.parse(JSON.stringify(value));
|
|
495
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const EnvUtils = {
|
|
2
|
+
/**
|
|
3
|
+
* Parses a boolean environment variable.
|
|
4
|
+
* Returns true for '1', 'true', 'yes', 'on' (case-insensitive).
|
|
5
|
+
* Returns defaultValue if the variable is undefined or empty.
|
|
6
|
+
*/
|
|
7
|
+
getBoolean(key, defaultValue = false) {
|
|
8
|
+
const val = process.env[key]?.toLowerCase().trim();
|
|
9
|
+
if (!val)
|
|
10
|
+
return defaultValue;
|
|
11
|
+
return this.isTrue(val);
|
|
12
|
+
},
|
|
13
|
+
/**
|
|
14
|
+
* Checks if a string value represents a true boolean.
|
|
15
|
+
*/
|
|
16
|
+
isTrue(val) {
|
|
17
|
+
return ['1', 'true', 'yes', 'on'].includes(val);
|
|
18
|
+
},
|
|
19
|
+
/**
|
|
20
|
+
* Parses a string environment variable.
|
|
21
|
+
* Returns defaultValue if the variable is undefined.
|
|
22
|
+
*/
|
|
23
|
+
getString(key, defaultValue = '') {
|
|
24
|
+
return process.env[key] || defaultValue;
|
|
25
|
+
},
|
|
26
|
+
/**
|
|
27
|
+
* Parses an integer environment variable.
|
|
28
|
+
* Returns defaultValue if the variable is undefined or not a valid number.
|
|
29
|
+
*/
|
|
30
|
+
getInt(key, defaultValue) {
|
|
31
|
+
const val = process.env[key];
|
|
32
|
+
if (!val)
|
|
33
|
+
return defaultValue;
|
|
34
|
+
const parsed = parseInt(val, 10);
|
|
35
|
+
return isNaN(parsed) ? defaultValue : parsed;
|
|
36
|
+
}
|
|
37
|
+
};
|