@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