+
+ {hasValidationWarnings && (
+
+ ⚠ {missingPlaceholders.length} placeholder(s) without params
+
+ )}
+ {hasValidationWarnings && (
+
+ Missing parameters: {missingPlaceholders.join(", ")}
+
+ )}
{props.config.template.content || "{}"}
diff --git a/src/models/Config.tsx b/src/models/Config.tsx
index 4c9fff9..b51417e 100644
--- a/src/models/Config.tsx
+++ b/src/models/Config.tsx
@@ -109,5 +109,50 @@ export class Config {
return missingParams;
}
+
+ /**
+ * Validates that all @placeholders@ in template have corresponding params.
+ * Checks DEFAULT env first, then all custom envs.
+ * Returns array of placeholder names that are not defined.
+ */
+ validatePlaceholders(): string[] {
+ const defaultEnv = this.envs.find(e => e.name === "DEFAULT");
+ const customEnvs = this.envs.filter(e => e.name !== "DEFAULT");
+
+ // Collect all param names from DEFAULT
+ const defaultParamNames = new Set(
+ defaultEnv?.params.map(p => p.name).filter(n => n && n.trim() !== "") || []
+ );
+
+ // Collect all param names from all custom envs
+ const customParamNames = new Set(
+ customEnvs.flatMap(e => e.params.map(p => p.name).filter(n => n && n.trim() !== ""))
+ );
+
+ // Extract all @placeholders@ from template
+ const placeholderRegex = /@(\w+)@/g;
+ const placeholdersInTemplate = new Set
();
+ let match;
+ while ((match = placeholderRegex.exec(this.template.content)) !== null) {
+ placeholdersInTemplate.add(match[1]);
+ }
+
+ // Find placeholders that don't have matching params
+ const missingParams: string[] = [];
+ for (const placeholder of placeholdersInTemplate) {
+ if (placeholder === Config.ENV_NAME_PARAM) continue; // Skip built-in
+
+ // Check if exists in DEFAULT or in ANY custom env
+ const inDefault = defaultParamNames.has(placeholder);
+ const inCustom = customParamNames.has(placeholder);
+
+ // Valid if: in DEFAULT, or in at least one custom env
+ if (!inDefault && !inCustom) {
+ missingParams.push(placeholder);
+ }
+ }
+
+ return missingParams;
+ }
}