diff --git a/.changeset/busy-sides-drive.md b/.changeset/busy-sides-drive.md new file mode 100644 index 000000000..7358e68c0 --- /dev/null +++ b/.changeset/busy-sides-drive.md @@ -0,0 +1,7 @@ +--- +'skuba': minor +--- + +init: Add support for private SEEK templates + +Adds support for downloading templates from the private SEEK-Jobs/skuba-templates repository. Users can now select "seek →" from the template list and specify a private template name, which will be downloaded using sparse checkout via SSH. diff --git a/src/cli/init/getConfig.ts b/src/cli/init/getConfig.ts index 892e0ccaa..fca96eb42 100644 --- a/src/cli/init/getConfig.ts +++ b/src/cli/init/getConfig.ts @@ -19,12 +19,13 @@ import { templateConfigSchema, } from '../../utils/template.js'; -import { downloadGitHubTemplate } from './git.js'; +import { downloadGitHubTemplate, downloadPrivateTemplate } from './git.js'; import { BASE_PROMPT_PROPS, type BaseFields, type Choice, getGitPath, + getPrivateTemplateName, getTemplateName, shouldContinue, } from './prompts.js'; @@ -94,12 +95,18 @@ const cloneTemplate = async ( templateName: string, destinationDir: string, ): Promise => { - const isCustomTemplate = templateName.startsWith('github:'); + const isGitHubTemplate = templateName.startsWith('github:'); + const isPrivateSeekTemplate = templateName.startsWith('seek:'); + const isCustomTemplate = isGitHubTemplate || isPrivateSeekTemplate; - if (isCustomTemplate) { + if (isGitHubTemplate) { const gitHubPath = templateName.slice('github:'.length); await downloadGitHubTemplate(gitHubPath, destinationDir); + } else if (isPrivateSeekTemplate) { + const privateName = templateName.slice('seek:'.length); + + await downloadPrivateTemplate(privateName, destinationDir); } else { const templateDir = path.join(TEMPLATE_DIR, templateName); @@ -140,6 +147,11 @@ const selectTemplateName = async () => { return `github:${gitHubPath}`; } + if (templateSelection === 'seek →') { + const privateName = await getPrivateTemplateName(); + return `seek:${privateName}`; + } + return templateSelection; }; diff --git a/src/cli/init/git.ts b/src/cli/init/git.ts index d3b7386ad..533e2b111 100644 --- a/src/cli/init/git.ts +++ b/src/cli/init/git.ts @@ -54,3 +54,52 @@ export const downloadGitHubTemplate = async ( recursive: true, }); }; + +export const downloadPrivateTemplate = async ( + templateName: string, + destinationDir: string, +) => { + log.newline(); + log.plain( + 'Downloading', + log.bold(templateName), + 'from SEEK-Jobs/skuba-templates', + ); + + const repoUrl = 'git@github.com:SEEK-Jobs/skuba-templates.git'; + const folderPath = `templates/${templateName}`; + const tempDir = `${destinationDir}_temp`; + + try { + await simpleGit().raw(['init', tempDir]); + + const sparseCheckoutPath = path.join( + tempDir, + '.git', + 'info', + 'sparse-checkout', + ); + await fs.promises.writeFile(sparseCheckoutPath, `${folderPath}/*\n`); + + await simpleGit(tempDir) + .raw(['config', 'core.sparseCheckout', 'true']) + .addRemote('origin', repoUrl) + .raw(['pull', 'origin', 'main', '--depth', '1', '--quiet']); + + const templatePath = path.join(tempDir, folderPath); + + try { + await fs.promises.access(templatePath); + } catch { + throw new Error(`Template "${templateName}" not found in repository`); + } + + await fs.ensureDir(destinationDir); + await fs.copy(templatePath, destinationDir); + + await fs.promises.rm(tempDir, { force: true, recursive: true }); + } catch (error) { + await fs.promises.rm(tempDir, { force: true, recursive: true }); + throw error; + } +}; diff --git a/src/cli/init/prompts.ts b/src/cli/init/prompts.ts index 00391e29c..852b0a3d8 100644 --- a/src/cli/init/prompts.ts +++ b/src/cli/init/prompts.ts @@ -119,3 +119,10 @@ export const getTemplateName = async () => message: 'Select a template:', choices: TEMPLATE_NAMES_WITH_BYO.map((name) => ({ name, value: name })), }); + +export const getPrivateTemplateName = async () => + input({ + message: 'Private SEEK template name', + validate: (value: string) => + value.length > 0 || 'Must be a valid template name', + }); diff --git a/src/utils/template.ts b/src/utils/template.ts index a3d2d5d9b..3b1496dd6 100644 --- a/src/utils/template.ts +++ b/src/utils/template.ts @@ -17,7 +17,11 @@ export const TEMPLATE_NAMES = [ export type TemplateName = (typeof TEMPLATE_NAMES)[number]; -export const TEMPLATE_NAMES_WITH_BYO = [...TEMPLATE_NAMES, 'github →'] as const; +export const TEMPLATE_NAMES_WITH_BYO = [ + ...TEMPLATE_NAMES, + 'github →', + 'seek →', +] as const; interface TemplateDocumentationConfig { /**