@forge/cli

12.19.012.19.0-experimental-a6c6519
out/service/module-service.js
+out/service/module-service.jsNew file
+158
Index: package/out/service/module-service.js
===================================================================
--- package/out/service/module-service.js
+++ package/out/service/module-service.js
@@ -0,0 +1,158 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.ModuleService = void 0;
+const tslib_1 = require("tslib");
+const fs_1 = tslib_1.__importDefault(require("fs"));
+const path_1 = tslib_1.__importDefault(require("path"));
+const yaml_1 = tslib_1.__importDefault(require("yaml"));
+const manifest_1 = require("@forge/manifest");
+const cli_shared_1 = require("@forge/cli-shared");
+const manifest_schema_json_1 = tslib_1.__importDefault(require("@forge/manifest/out/schema/manifest-schema.json"));
+class ModuleService {
+    templateServices;
+    cacheDirs = [];
+    constructor(templateServices) {
+        this.templateServices = templateServices;
+    }
+    async getAvailableModules(product) {
+        const availableModules = await this.templateServices.getAvailableModules(product);
+        const moduleMap = new Map();
+        for (const t of availableModules) {
+            const key = t.moduleKey;
+            const existing = moduleMap.get(key);
+            if (!existing) {
+                moduleMap.set(key, t);
+            }
+        }
+        return moduleMap;
+    }
+    getModuleChoice(moduleMap) {
+        const moduleChoiceMap = new Map();
+        for (const meta of moduleMap.values()) {
+            const moduleChoice = meta.description
+                ? `${meta.name} — ${meta.displayName}: ${meta.description}`
+                : `${meta.name} — ${meta.displayName}`;
+            moduleChoiceMap.set(moduleChoice, meta);
+        }
+        return moduleChoiceMap;
+    }
+    async downloadSelectedTemplate(template, uiFramework) {
+        if (!this.templateServices)
+            return undefined;
+        const downloadedMeta = await this.templateServices.downloadModuleAssets(template, uiFramework);
+        if (downloadedMeta.cacheDir) {
+            this.cacheDirs.push(downloadedMeta.cacheDir);
+        }
+        return downloadedMeta;
+    }
+    cleanup() {
+        if (this.templateServices) {
+            for (const dir of this.cacheDirs) {
+                this.templateServices.cleanupCache(dir);
+            }
+        }
+        this.cacheDirs.length = 0;
+    }
+    parseManifestAndExtractKeys(content, filePath, moduleKey) {
+        const keys = {
+            moduleKeys: new Set(),
+            functionKeys: new Set(),
+            resourceKeys: new Set()
+        };
+        try {
+            const parsed = new manifest_1.ManifestParserBuilder().withInterpolators().build().parseManifest({ content, filePath });
+            if (moduleKey && parsed?.modules && Object.prototype.hasOwnProperty.call(parsed.modules, moduleKey)) {
+                const moduleEntries = (parsed.modules[moduleKey] || []);
+                keys.moduleKeys = new Set(moduleEntries.map((m) => m?.key).filter((k) => Boolean(k)));
+            }
+            const funcEntries = parsed?.modules?.function || [];
+            keys.functionKeys = new Set(funcEntries.map((f) => f?.key).filter(Boolean));
+            const resourceEntries = parsed?.resources || [];
+            keys.resourceKeys = new Set(resourceEntries.map((r) => r?.key).filter(Boolean));
+            return keys;
+        }
+        catch {
+            return {
+                moduleKeys: new Set(),
+                functionKeys: new Set(),
+                resourceKeys: new Set()
+            };
+        }
+    }
+    readManifestFragment(templateCacheDir) {
+        const fragmentPath = path_1.default.join(templateCacheDir, `manifest-fragment.yml`);
+        try {
+            return fs_1.default.readFileSync(fragmentPath, 'utf8');
+        }
+        catch {
+            return undefined;
+        }
+    }
+    checkDuplicateKey(varDef, value, existingKeys, moduleKey) {
+        if (varDef.name === 'moduleKey' && existingKeys.moduleKeys.has(value)) {
+            return cli_shared_1.Text.module.errors.duplicateModuleKey(value, moduleKey);
+        }
+        if (varDef.name === 'functionKey' && existingKeys.functionKeys.has(value)) {
+            return cli_shared_1.Text.module.errors.duplicateFunctionKey(value);
+        }
+        if (varDef.name === 'resourceKey' && existingKeys.resourceKeys.has(value)) {
+            return cli_shared_1.Text.module.errors.duplicateResourceKey(value);
+        }
+        return undefined;
+    }
+    async validateFragment(substitutedFragment, manifestPath, currentValue, moduleKey) {
+        const fragment = yaml_1.default.parse(substitutedFragment);
+        const existingManifest = yaml_1.default.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
+        if (!fragment.app && existingManifest.app) {
+            fragment.app = existingManifest.app;
+        }
+        const yamlString = yaml_1.default.stringify(fragment);
+        const schemaValidator = new manifest_1.SchemaValidator(manifest_schema_json_1.default);
+        const result = await schemaValidator.validate({ yamlContent: fragment, yamlContentByLine: yamlString.split('\n') });
+        if (!result.success && result.errors) {
+            const messages = [];
+            const quotedValue = `'${currentValue}'`;
+            for (const e of result.errors) {
+                if (e.level !== 'error' || !e.message.includes(quotedValue) || !e.message.includes(moduleKey))
+                    continue;
+                messages.push(e.message);
+            }
+            return messages;
+        }
+        return [];
+    }
+    getVariableDefinitions(template, uiFramework) {
+        if (uiFramework && template.variants?.[uiFramework]?.variables) {
+            return template.variants[uiFramework].variables ?? [];
+        }
+        if (template.variables && template.variables.length > 0) {
+            return template.variables;
+        }
+        return [];
+    }
+    loadManifestContext(moduleKey) {
+        const manifestPath = path_1.default.join(process.cwd(), 'manifest.yml');
+        const manifestContent = fs_1.default.readFileSync(manifestPath, 'utf8');
+        const existingKeys = this.parseManifestAndExtractKeys(manifestContent, manifestPath, moduleKey);
+        return { manifestPath, existingKeys };
+    }
+    computeEffectiveDefault(varDef, variables) {
+        if (varDef.name === 'resourceKey' && variables['moduleKey']) {
+            return `${variables['moduleKey']}-res`;
+        }
+        return varDef.default;
+    }
+    async validateCandidateValue(varDef, value, existingKeys, moduleKey, fragmentContent, variables, manifestPath) {
+        const duplicateWarning = this.checkDuplicateKey(varDef, value, existingKeys, moduleKey);
+        if (duplicateWarning) {
+            return [duplicateWarning];
+        }
+        if (fragmentContent) {
+            const substituted = fragmentContent.replace(/\{\{\s*([a-zA-Z0-9_-]+)\s*\}\}/g, (_m, key) => key === varDef.name ? value : variables[key] ?? '');
+            return this.validateFragment(substituted, manifestPath, value, moduleKey);
+        }
+        return [];
+    }
+}
+exports.ModuleService = ModuleService;
+//# sourceMappingURL=module-service.js.map
\ No newline at end of file