Files
pbakaus_impeccable/scripts/lib/transformers/factory.js
T
Paul BakausandClaude Opus 4.6 c2b72b9d44 Fix: factory transformer was not copying skill scripts to dist
The refactored factory.js transformer dropped script file support that
existed in the old shared.js version. Scripts were read from source
but never written to dist/, so npx skills installed skills without the
cleanup-deprecated.mjs script, causing errors on first load.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 09:43:50 -07:00

132 lines
4.6 KiB
JavaScript

import path from 'path';
import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceholders, prefixSkillReferences, PROVIDER_PLACEHOLDERS } from '../utils.js';
/**
* Map from frontmatter field name to extraction spec.
*
* - sourceKey: property name on the skill object
* - yamlKey: key name in YAML frontmatter
* - condition: if provided, field is only emitted when this returns true
* - value: if provided, use this instead of skill[sourceKey]
*/
const FIELD_SPECS = {
'user-invocable': {
sourceKey: 'userInvocable',
yamlKey: 'user-invocable',
condition: (skill) => skill.userInvocable,
value: () => true,
},
'argument-hint': {
sourceKey: 'argumentHint',
yamlKey: 'argument-hint',
condition: (skill) => skill.userInvocable && skill.argumentHint,
},
license: {
sourceKey: 'license',
yamlKey: 'license',
},
compatibility: {
sourceKey: 'compatibility',
yamlKey: 'compatibility',
},
metadata: {
sourceKey: 'metadata',
yamlKey: 'metadata',
},
'allowed-tools': {
sourceKey: 'allowedTools',
yamlKey: 'allowed-tools',
},
};
/**
* Create a transformer function for a given provider config.
*
* @param {Object} config - Provider configuration from providers.js
* @returns {Function} transform(skills, distDir, options?)
*/
export function createTransformer(config) {
const { provider, configDir, displayName, frontmatterFields = [], bodyTransform, placeholderProvider } = config;
const placeholderKey = placeholderProvider || provider;
const activeFields = frontmatterFields
.map((name) => FIELD_SPECS[name])
.filter(Boolean);
return function transform(skills, distDir, options = {}) {
const { prefix = '', outputSuffix = '' } = options;
const providerDir = path.join(distDir, `${provider}${outputSuffix}`);
const skillsDir = path.join(providerDir, `${configDir}/skills`);
cleanDir(providerDir);
ensureDir(skillsDir);
const allSkillNames = skills.map((s) => s.name);
const commandNames = skills
.filter((s) => s.userInvocable)
.map((s) => `${prefix}${s.name}`);
let refCount = 0;
let scriptCount = 0;
for (const skill of skills) {
const skillName = `${prefix}${skill.name}`;
const skillDir = path.join(skillsDir, skillName);
// Build frontmatter
const frontmatterObj = {
name: skillName,
description: skill.description,
};
for (const spec of activeFields) {
if (spec.condition && !spec.condition(skill)) continue;
const val = spec.value ? spec.value(skill) : skill[spec.sourceKey];
if (val) frontmatterObj[spec.yamlKey] = val;
}
const frontmatter = generateYamlFrontmatter(frontmatterObj);
// Build body
const cmdPrefix = (PROVIDER_PLACEHOLDERS[placeholderKey] || {}).command_prefix || '/';
let skillBody = replacePlaceholders(skill.body, placeholderKey, commandNames, allSkillNames);
// Replace {{scripts_path}} with provider-aware path to skill's scripts directory
const scriptsPath = `${configDir}/skills/${skillName}/scripts`;
skillBody = skillBody.replace(/\{\{scripts_path\}\}/g, scriptsPath);
if (prefix) skillBody = prefixSkillReferences(skillBody, prefix, allSkillNames, cmdPrefix);
if (bodyTransform) skillBody = bodyTransform(skillBody, skill);
const content = `${frontmatter}\n\n${skillBody}`;
writeFile(path.join(skillDir, 'SKILL.md'), content);
// Copy reference files
if (skill.references && skill.references.length > 0) {
const refDir = path.join(skillDir, 'reference');
ensureDir(refDir);
for (const ref of skill.references) {
const refContent = replacePlaceholders(ref.content, placeholderKey, [], allSkillNames);
writeFile(path.join(refDir, `${ref.name}.md`), refContent);
refCount++;
}
}
// Copy script files
if (skill.scripts && skill.scripts.length > 0) {
const scriptsOutDir = path.join(skillDir, 'scripts');
ensureDir(scriptsOutDir);
for (const script of skill.scripts) {
writeFile(path.join(scriptsOutDir, script.name), script.content);
scriptCount++;
}
}
}
const userInvocableCount = skills.filter((s) => s.userInvocable).length;
const refInfo = refCount > 0 ? ` (${refCount} reference files)` : '';
const scriptInfo = scriptCount > 0 ? ` (${scriptCount} script files)` : '';
const prefixInfo = prefix ? ` [${prefix}prefixed]` : '';
console.log(`✓ ${displayName}${prefixInfo}: ${skills.length} skills (${userInvocableCount} user-invocable)${refInfo}${scriptInfo}`);
};
}