Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions pgpm/cli/src/commands/init/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { execSync } from 'child_process';
import fs from 'fs';
import path from 'path';

import {
BoilerplateSkill,
DEFAULT_TEMPLATE_REPO,
DEFAULT_TEMPLATE_TOOL_NAME,
inspectTemplate,
Expand Down Expand Up @@ -275,6 +277,38 @@ interface InitContext {
createWorkspace?: boolean;
}

function installSkills(skills: BoilerplateSkill[], cwd: string): void {
const failed: string[] = [];

for (const entry of skills) {
const source = entry.source.includes('://')
? entry.source
: `https://github.com/${entry.source}`;

for (const skill of entry.skills) {
const cmd = `npx --yes skills add ${source} --skill ${skill} --yes`;
try {
execSync(cmd, {
cwd,
stdio: ['pipe', 'inherit', 'inherit'],
timeout: 120_000,
});
} catch {
failed.push(` npx skills add ${source} --skill ${skill}`);
}
}
}

if (failed.length > 0) {
process.stdout.write('\n⚠️ Some skills could not be installed automatically.\n');
process.stdout.write('Run the following commands manually:\n\n');
for (const cmd of failed) {
process.stdout.write(`${cmd}\n`);
}
process.stdout.write('\n');
}
}

async function handleWorkspaceInit(
argv: Partial<Record<string, any>>,
prompter: Inquirerer,
Expand Down Expand Up @@ -329,6 +363,19 @@ async function handleWorkspaceInit(
process.stdout.write('\n');
}

// Install skills declared in .boilerplate.json
const templateInfo = inspectTemplate({
fromPath: ctx.fromPath,
templateRepo: ctx.templateRepo,
branch: ctx.branch,
dir: ctx.dir,
cwd: ctx.cwd,
});
if (templateInfo.config?.skills?.length) {
process.stdout.write('\n📦 Installing skills...\n\n');
installSkills(templateInfo.config.skills, targetPath);
}

const relPath = path.relative(process.cwd(), targetPath);
process.stdout.write(`\n✨ Enjoy!\n\ncd ./${relPath}\n`);

Expand Down Expand Up @@ -620,6 +667,20 @@ async function handleModuleInit(
process.stdout.write('\n');
}

// Install skills declared in .boilerplate.json
const moduleTemplateInfo = inspectTemplate({
fromPath: ctx.fromPath,
templateRepo: ctx.templateRepo,
branch: ctx.branch,
dir: ctx.dir,
cwd: ctx.cwd,
});
if (moduleTemplateInfo.config?.skills?.length) {
const skillsCwd = project.workspacePath || modulePath;
process.stdout.write('\n📦 Installing skills...\n\n');
installSkills(moduleTemplateInfo.config.skills, skillsCwd);
}

const relPath = path.relative(process.cwd(), modulePath);
process.stdout.write(`\n✨ Enjoy!\n\ncd ./${relPath}\n`);

Expand Down
17 changes: 15 additions & 2 deletions pgpm/core/src/core/template-scaffold.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,15 @@ import type { Inquirerer, Question } from 'inquirerer';
export type WorkspaceType = 'pgpm' | 'pnpm' | 'lerna' | 'npm' | false;

/**
* Extended BoilerplateConfig that adds workspace requirement field.
* This field controls both workspace detection and whether pgpm-specific files are created.
* Declares a skill to install after scaffolding completes.
*/
export interface BoilerplateSkill {
/** GitHub repository (org/repo format) */
source: string;
/** Skill name(s) to install from the source */
skills: string[];
}

export interface BoilerplateConfig extends GenomicBoilerplateConfig {
/**
* Specifies what type of workspace this template requires.
Expand All @@ -29,6 +35,13 @@ export interface BoilerplateConfig extends GenomicBoilerplateConfig {
* Defaults to 'pgpm' for 'module' type (backward compatibility), false for others.
*/
requiresWorkspace?: WorkspaceType;
/**
* Skills to install after scaffolding completes.
* Each entry specifies a source repository and skill names to install.
* Runs `npx skills add <source> --skill <name>` for each entry.
* Non-fatal: prints manual install commands on failure.
*/
skills?: BoilerplateSkill[];
}

export interface InspectTemplateOptions {
Expand Down
Loading