@launchql/core
Version:
LaunchQL Package and Migration Tools
169 lines (168 loc) • 7.21 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DEFAULT_TEMPLATE_TOOL_NAME = exports.DEFAULT_TEMPLATE_TTL_MS = exports.DEFAULT_TEMPLATE_REPO = void 0;
exports.scaffoldTemplate = scaffoldTemplate;
const fs_1 = __importDefault(require("fs"));
const os_1 = __importDefault(require("os"));
const path_1 = __importDefault(require("path"));
const create_gen_app_1 = require("create-gen-app");
const boilerplate_scanner_1 = require("./boilerplate-scanner");
exports.DEFAULT_TEMPLATE_REPO = 'https://github.com/constructive-io/pgpm-boilerplates.git';
exports.DEFAULT_TEMPLATE_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 1 week
exports.DEFAULT_TEMPLATE_TOOL_NAME = 'pgpm';
const templatizer = new create_gen_app_1.Templatizer();
const looksLikePath = (value) => {
return (value.startsWith('.') || value.startsWith('/') || value.startsWith('~'));
};
const normalizeQuestions = (questions) => questions?.map((q) => ({
...q,
type: q.type || 'text',
}));
const attachQuestionsToTemplatizer = (templ, questions) => {
if (!questions?.length || typeof templ?.extract !== 'function')
return;
const originalExtract = templ.extract.bind(templ);
templ.extract = async (templateDir) => {
const extracted = await originalExtract(templateDir);
extracted.projectQuestions = {
questions: normalizeQuestions(questions),
};
return extracted;
};
};
/**
* Resolve the template path using the new metadata-driven resolution.
*
* Resolution order:
* 1. If explicit `templatePath` is provided, use it directly
* 2. If `.boilerplates.json` exists, use its `dir` field to find the base directory
* 3. Look for `{baseDir}/{type}` (e.g., "default/module")
* 4. Fallback to legacy structure: `{type}` directly in root
*/
const resolveFromPath = (templateDir, templatePath, type, dirOverride) => {
// If explicit templatePath is provided, use it directly
if (templatePath) {
const candidateDir = path_1.default.isAbsolute(templatePath)
? templatePath
: path_1.default.join(templateDir, templatePath);
if (fs_1.default.existsSync(candidateDir) &&
fs_1.default.statSync(candidateDir).isDirectory()) {
return {
fromPath: path_1.default.relative(templateDir, candidateDir) || '.',
resolvedTemplatePath: candidateDir,
};
}
return {
fromPath: templatePath,
resolvedTemplatePath: path_1.default.join(templateDir, templatePath),
};
}
// Try new metadata-driven resolution
const rootConfig = (0, boilerplate_scanner_1.readBoilerplatesConfig)(templateDir);
const baseDir = dirOverride ?? rootConfig?.dir;
if (baseDir) {
// New structure: {templateDir}/{baseDir}/{type}
const newStructurePath = path_1.default.join(templateDir, baseDir, type);
if (fs_1.default.existsSync(newStructurePath) &&
fs_1.default.statSync(newStructurePath).isDirectory()) {
return {
fromPath: path_1.default.join(baseDir, type),
resolvedTemplatePath: newStructurePath,
};
}
}
// Fallback to legacy structure: {templateDir}/{type}
const legacyPath = path_1.default.join(templateDir, type);
if (fs_1.default.existsSync(legacyPath) && fs_1.default.statSync(legacyPath).isDirectory()) {
return {
fromPath: type,
resolvedTemplatePath: legacyPath,
};
}
// Default fallback
return {
fromPath: type,
resolvedTemplatePath: path_1.default.join(templateDir, type),
};
};
async function scaffoldTemplate(options) {
const { type, outputDir, templateRepo = exports.DEFAULT_TEMPLATE_REPO, branch, templatePath, answers, noTty = false, cacheTtlMs = exports.DEFAULT_TEMPLATE_TTL_MS, toolName = exports.DEFAULT_TEMPLATE_TOOL_NAME, cwd, cacheBaseDir, dir, } = options;
const resolvedRepo = looksLikePath(templateRepo)
? path_1.default.resolve(cwd ?? process.cwd(), templateRepo)
: templateRepo;
// Handle local template directories without caching
if (looksLikePath(templateRepo) &&
fs_1.default.existsSync(resolvedRepo) &&
fs_1.default.statSync(resolvedRepo).isDirectory()) {
const { fromPath, resolvedTemplatePath } = resolveFromPath(resolvedRepo, templatePath, type, dir);
// Read boilerplate config for questions
const boilerplateConfig = (0, boilerplate_scanner_1.readBoilerplateConfig)(resolvedTemplatePath);
// Inject questions into the templatizer pipeline so prompt types and defaults are applied
attachQuestionsToTemplatizer(templatizer, boilerplateConfig?.questions);
await templatizer.process(resolvedRepo, outputDir, {
argv: answers,
noTty,
fromPath,
});
return {
cacheUsed: false,
cacheExpired: false,
templateDir: resolvedRepo,
questions: boilerplateConfig?.questions,
};
}
// Remote repo with caching
const cacheManager = new create_gen_app_1.CacheManager({
toolName,
ttl: cacheTtlMs,
baseDir: cacheBaseDir ??
process.env.PGPM_CACHE_BASE_DIR ??
(process.env.JEST_WORKER_ID
? path_1.default.join(os_1.default.tmpdir(), `pgpm-cache-${process.env.JEST_WORKER_ID}`)
: undefined),
});
const gitCloner = new create_gen_app_1.GitCloner();
const normalizedUrl = gitCloner.normalizeUrl(resolvedRepo);
const cacheKey = cacheManager.createKey(normalizedUrl, branch);
const expiredMetadata = cacheManager.checkExpiration(cacheKey);
if (expiredMetadata) {
cacheManager.clear(cacheKey);
}
let templateDir;
let cacheUsed = false;
const cachedPath = cacheManager.get(cacheKey);
if (cachedPath && !expiredMetadata) {
templateDir = cachedPath;
cacheUsed = true;
}
else {
const tempDest = path_1.default.join(cacheManager.getReposDir(), cacheKey);
gitCloner.clone(normalizedUrl, tempDest, {
branch,
depth: 1,
singleBranch: true,
});
cacheManager.set(cacheKey, tempDest);
templateDir = tempDest;
}
const { fromPath, resolvedTemplatePath } = resolveFromPath(templateDir, templatePath, type, dir);
// Read boilerplate config for questions
const boilerplateConfig = (0, boilerplate_scanner_1.readBoilerplateConfig)(resolvedTemplatePath);
// Inject questions into the templatizer pipeline so prompt types and defaults are applied
attachQuestionsToTemplatizer(templatizer, boilerplateConfig?.questions);
await templatizer.process(templateDir, outputDir, {
argv: answers,
noTty,
fromPath,
});
return {
cacheUsed,
cacheExpired: Boolean(expiredMetadata),
cachePath: templateDir,
templateDir,
questions: boilerplateConfig?.questions,
};
}