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
2 changes: 1 addition & 1 deletion packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ lql init --template-path ./custom-templates/module

**Options:**

- `--repo <repo>` - Template repo (default: `https://github.com/launchql/pgpm-boilerplates.git`)
- `--repo <repo>` - Template repo (default: `https://github.com/constructive-io/pgpm-boilerplates.git`)
- `--template-path <path>` - Template sub-path (defaults to `workspace`/`module`) or local path override
- `--from-branch <branch>` - Branch/tag when cloning the template repo

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ exports[`cmds:extension runs \`extension\` command after workspace and module se
"README.md",
"Makefile",
"LICENSE",
".boilerplate.json",
"__tests__/basic.test.ts",
]
`;
Expand Down
19 changes: 11 additions & 8 deletions packages/cli/__tests__/__snapshots__/init.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ exports[`cmds:init initializes module: module-only - argv 1`] = `
"plpgsql",
"citext",
],
"fromBranch": "restructuring",
"fullName": "Tester",
"license": "MIT",
"moduleDesc": "my-module",
"moduleName": "my-module",
"name": "my-module",
"packageIdentifier": "my-module",
"repo": "https://github.com/launchql/pgpm-boilerplates.git",
"repo": "https://github.com/constructive-io/pgpm-boilerplates.git",
"repoName": "my-module",
"templatePath": "module",
"username": "tester",
}
`;
Expand All @@ -48,13 +48,15 @@ exports[`cmds:init initializes module: module-only - files 1`] = `
".prettierrc.json",
".gitignore",
".eslintrc.json",
".boilerplate.json",
"packages/my-module/pgpm.plan",
"packages/my-module/package.json",
"packages/my-module/my-module.control",
"packages/my-module/jest.config.js",
"packages/my-module/README.md",
"packages/my-module/Makefile",
"packages/my-module/LICENSE",
"packages/my-module/.boilerplate.json",
"packages/my-module/__tests__/basic.test.ts",
".github/ci.yaml",
]
Expand All @@ -72,15 +74,15 @@ exports[`cmds:init initializes module: module-only - result 1`] = `
"plpgsql",
"citext",
],
"fromBranch": "restructuring",
"fullName": "Tester",
"license": "MIT",
"moduleDesc": "my-module",
"moduleName": "my-module",
"name": "my-module",
"packageIdentifier": "my-module",
"repo": "https://github.com/launchql/pgpm-boilerplates.git",
"repo": "https://github.com/constructive-io/pgpm-boilerplates.git",
"repoName": "my-module",
"templatePath": "module",
"username": "tester",
}
`;
Expand All @@ -98,15 +100,15 @@ exports[`cmds:init initializes workspace: workspace - argv 1`] = `
"access": "public",
"cwd": "<CWD>",
"email": "tester@example.com",
"fromBranch": "restructuring",
"fullName": "Tester",
"license": "MIT",
"moduleDesc": "my-workspace",
"moduleName": "starter-module",
"name": "my-workspace",
"packageIdentifier": "my-workspace",
"repo": "https://github.com/launchql/pgpm-boilerplates.git",
"repo": "https://github.com/constructive-io/pgpm-boilerplates.git",
"repoName": "my-workspace",
"templatePath": "workspace",
"username": "tester",
"workspace": true,
}
Expand All @@ -124,6 +126,7 @@ exports[`cmds:init initializes workspace: workspace - files 1`] = `
"my-workspace/.prettierrc.json",
"my-workspace/.gitignore",
"my-workspace/.eslintrc.json",
"my-workspace/.boilerplate.json",
"my-workspace/.github/ci.yaml",
]
`;
Expand All @@ -137,15 +140,15 @@ exports[`cmds:init initializes workspace: workspace - result 1`] = `
"access": "public",
"cwd": "<CWD>",
"email": "tester@example.com",
"fromBranch": "restructuring",
"fullName": "Tester",
"license": "MIT",
"moduleDesc": "my-workspace",
"moduleName": "starter-module",
"name": "my-workspace",
"packageIdentifier": "my-workspace",
"repo": "https://github.com/launchql/pgpm-boilerplates.git",
"repo": "https://github.com/constructive-io/pgpm-boilerplates.git",
"repoName": "my-workspace",
"templatePath": "workspace",
"username": "tester",
"workspace": true,
}
Expand Down
8 changes: 5 additions & 3 deletions packages/cli/__tests__/init.templates.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import path from 'path';

import { scaffoldTemplate } from '@launchql/core';

const TEMPLATE_REPO = 'https://github.com/launchql/pgpm-boilerplates.git';
const TEMPLATE_REPO = 'https://github.com/constructive-io/pgpm-boilerplates.git';

describe('Template scaffolding', () => {
it('processes workspace template from default repo', async () => {
Expand All @@ -16,7 +16,8 @@ describe('Template scaffolding', () => {
type: 'workspace',
outputDir: outDir,
templateRepo: TEMPLATE_REPO,
templatePath: 'workspace',
branch: 'restructuring', // TODO: remove after merging restructuring to main
templatePath: 'default/workspace',
answers: {
name: 'demo-workspace',
fullName: 'Tester',
Expand All @@ -40,7 +41,8 @@ describe('Template scaffolding', () => {
type: 'module',
outputDir: outDir,
templateRepo: TEMPLATE_REPO,
templatePath: 'module',
branch: 'restructuring', // TODO: remove after merging restructuring to main
templatePath: 'default/module',
answers: {
name: 'demo-module',
description: 'demo module',
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/__tests__/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ describe('cmds:init', () => {
cwd: fixture.tempDir,
name: 'test-workspace-template',
workspace: true,
templatePath: 'workspace'
templatePath: 'default/workspace'
});

await commands(argv, prompter, {
Expand Down Expand Up @@ -171,7 +171,7 @@ describe('cmds:init', () => {
name: 'test-module-template',
moduleName: 'test-module-template',
extensions: ['plpgsql'],
templatePath: 'module'
templatePath: 'default/module'
}), prompter, {
noTty: true,
input: mockInput,
Expand Down Expand Up @@ -236,7 +236,7 @@ describe('cmds:init', () => {
cwd: fixture.tempDir,
name: 'test-workspace-branch',
workspace: true,
fromBranch: 'main'
fromBranch: 'restructuring' // TODO: change back to 'main' after restructuring is merged
});

await commands(argv, prompter, {
Expand Down
32 changes: 17 additions & 15 deletions packages/cli/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,28 @@ const createCommandMap = (skipPgTeardown: boolean = false): Record<string, Funct
};

export const commands = async (argv: Partial<ParsedArgs>, prompter: Inquirerer, options: CLIOptions & { skipPgTeardown?: boolean }) => {
let { first: command, newArgv } = extractFirst(argv);

// Run update check early so it shows on help/version paths too
try {
const pkg = readAndParsePackageJson();
await checkForUpdates({
command: command || 'help',
pkgName: pkg.name,
pkgVersion: pkg.version,
toolName: 'lql',
key: pkg.name,
updateCommand: `Run npm i -g ${pkg.name}@latest to upgrade.`
});
} catch {
// ignore update check failures
}

if (argv.version || argv.v) {
const pkg = readAndParsePackageJson();
console.log(pkg.version);
process.exit(0);
}
let { first: command, newArgv } = extractFirst(argv);

// Show usage if explicitly requested but no command specified
if ((argv.help || argv.h || command === 'help') && !command) {
Expand Down Expand Up @@ -62,20 +78,6 @@ export const commands = async (argv: Partial<ParsedArgs>, prompter: Inquirerer,
command = answer.command;
}

try {
const pkg = readAndParsePackageJson();
await checkForUpdates({
command,
pkgName: pkg.name,
pkgVersion: pkg.version,
toolName: 'lql',
key: pkg.name,
updateCommand: `Run npm i -g ${pkg.name}@latest to upgrade.`
});
} catch {
// ignore update check failures
}

// Prompt for working directory
newArgv = await prompter.prompt(newArgv, [
{
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/test-utils/init-argv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export const withInitDefaults = (argv: ParsedArgs, defaultRepo: string = DEFAULT
return {
...args,
repo: args.repo ?? defaultRepo,
templatePath: args.templatePath ?? (args.workspace ? 'workspace' : 'module')
// TODO: remove fromBranch after merging restructuring to main
fromBranch: args.fromBranch ?? 'restructuring'
// Don't set default templatePath - let scaffoldTemplate use metadata-driven resolution from .boilerplates.json
};
};
117 changes: 117 additions & 0 deletions packages/core/src/core/boilerplate-scanner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import fs from 'fs';
import path from 'path';

import {
BoilerplateConfig,
BoilerplatesRootConfig,
ScannedBoilerplate
} from './boilerplate-types';

/**
* Read the root `.boilerplates.json` configuration from a template repository.
* This file specifies the default directory containing boilerplate templates.
*
* @param templateDir - The root directory of the template repository
* @returns The root config or null if not found
*/
export function readBoilerplatesConfig(templateDir: string): BoilerplatesRootConfig | null {
const configPath = path.join(templateDir, '.boilerplates.json');
if (fs.existsSync(configPath)) {
try {
const content = fs.readFileSync(configPath, 'utf-8');
return JSON.parse(content) as BoilerplatesRootConfig;
} catch {
return null;
}
}
return null;
}

/**
* Read the `.boilerplate.json` configuration from a boilerplate directory.
* This file specifies the boilerplate type and questions.
*
* @param boilerplatePath - The path to the boilerplate directory
* @returns The boilerplate config or null if not found
*/
export function readBoilerplateConfig(boilerplatePath: string): BoilerplateConfig | null {
const jsonPath = path.join(boilerplatePath, '.boilerplate.json');

if (fs.existsSync(jsonPath)) {
try {
const content = fs.readFileSync(jsonPath, 'utf-8');
return JSON.parse(content) as BoilerplateConfig;
} catch {
return null;
}
}

return null;
}

/**
* Scan a base directory for boilerplate templates.
* Each subdirectory with a `.boilerplate.json` file is considered a boilerplate.
*
* @param baseDir - The directory to scan (e.g., "default/")
* @returns Array of scanned boilerplates with their configurations
*/
export function scanBoilerplates(baseDir: string): ScannedBoilerplate[] {
const boilerplates: ScannedBoilerplate[] = [];

if (!fs.existsSync(baseDir)) {
return boilerplates;
}

const entries = fs.readdirSync(baseDir, { withFileTypes: true });

for (const entry of entries) {
if (!entry.isDirectory()) {
continue;
}

const boilerplatePath = path.join(baseDir, entry.name);
const config = readBoilerplateConfig(boilerplatePath);

if (config) {
boilerplates.push({
name: entry.name,
path: boilerplatePath,
type: config.type ?? 'module',
questions: config.questions
});
}
}

return boilerplates;
}

/**
* Find a boilerplate by type within a scanned list.
*
* @param boilerplates - Array of scanned boilerplates
* @param type - The type to find ('workspace' or 'module')
* @returns The matching boilerplate or undefined
*/
export function findBoilerplateByType(
boilerplates: ScannedBoilerplate[],
type: 'workspace' | 'module'
): ScannedBoilerplate | undefined {
return boilerplates.find((bp) => bp.type === type);
}

/**
* Resolve the base directory for boilerplates in a template repository.
* Uses `.boilerplates.json` if present, otherwise returns empty string.
*
* @param templateDir - The root directory of the template repository
* @returns The resolved base directory path
*/
export function resolveBoilerplateBaseDir(templateDir: string): string {
const rootConfig = readBoilerplatesConfig(templateDir);
if (rootConfig?.dir) {
return path.join(templateDir, rootConfig.dir);
}
return templateDir;
}

Loading