@quasarbright/projection
Version:
A static site generator that creates a beautiful, interactive gallery to showcase your coding projects. Features search, filtering, tags, responsive design, and an admin UI.
382 lines (371 loc) • 14 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.init = init;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const readline = __importStar(require("readline"));
const logger_1 = require("../utils/logger");
const git_helper_1 = require("../utils/git-helper");
const deployment_config_1 = require("../utils/deployment-config");
/**
* Display help for the init command
*/
function showInitHelp() {
console.log(`
Projection Init - Initialize a New Project
USAGE:
projection init [options]
DESCRIPTION:
Creates a new Projection project in the current directory with
sample project data, configuration, and documentation.
OPTIONS:
--force Overwrite existing files without prompting
--format <fmt> Choose data format: yaml or json (default: yaml)
--minimal Create minimal example instead of full sample
--help Show this help message
EXAMPLES:
projection init # Initialize with YAML format
projection init --format json # Initialize with JSON format
projection init --minimal # Create minimal example
projection init --force # Overwrite existing files
CREATED FILES:
projects.yaml (or .json) Project data file
projection.config.json Site configuration
.gitignore Git ignore patterns
README.md Project documentation
`);
}
/**
* Initialize a new Projection project in the current directory
*/
async function init(options = {}) {
if (options.help) {
showInitHelp();
return;
}
const cwd = process.cwd();
const format = options.format || 'yaml';
const projectsFileName = format === 'yaml' ? 'projects.yaml' : 'projects.json';
const configFileName = 'projection.config.json';
const gitignoreFileName = '.gitignore';
const readmeFileName = 'README.md';
const projectsFilePath = path.join(cwd, projectsFileName);
const configFilePath = path.join(cwd, configFileName);
const gitignorePath = path.join(cwd, gitignoreFileName);
const readmePath = path.join(cwd, readmeFileName);
// Check for existing files
const projectsExists = fs.existsSync(projectsFilePath);
const configExists = fs.existsSync(configFilePath);
const gitignoreExists = fs.existsSync(gitignorePath);
const readmeExists = fs.existsSync(readmePath);
if ((projectsExists || configExists || gitignoreExists || readmeExists) && !options.force) {
const existingFiles = [];
if (projectsExists)
existingFiles.push(projectsFileName);
if (configExists)
existingFiles.push(configFileName);
if (gitignoreExists)
existingFiles.push(gitignoreFileName);
if (readmeExists)
existingFiles.push(readmeFileName);
logger_1.Logger.newline();
logger_1.Logger.warn('The following files already exist:');
logger_1.Logger.list(existingFiles);
logger_1.Logger.newline();
logger_1.Logger.dim('Use --force to overwrite existing files.');
logger_1.Logger.newline();
const shouldContinue = await promptUser('Do you want to overwrite these files? (y/N): ');
if (!shouldContinue.toLowerCase().startsWith('y')) {
logger_1.Logger.newline();
logger_1.Logger.error('Initialization cancelled.');
logger_1.Logger.newline();
return;
}
}
// Get template directory path
const templateDir = getTemplateDirectory();
// Detect Git repository and extract deployment info
const gitInfo = await detectGitRepository(cwd);
// Copy projects file
await copyProjectsTemplate(templateDir, projectsFilePath, format, options.minimal);
// Copy config file with deployment support
await copyConfigTemplate(templateDir, configFilePath, gitInfo);
// Create .gitignore file
await createGitignoreFile(cwd, gitignorePath);
// Create README.md file
await createReadmeFile(templateDir, readmePath);
// Display success message with deployment instructions
displaySuccessMessage(projectsFileName, configFileName, gitignoreFileName, readmeFileName, gitInfo);
}
/**
* Detect Git repository and extract deployment information
*/
async function detectGitRepository(cwd) {
const result = {
isGitRepo: false,
hasRemote: false,
repositoryUrl: null,
baseUrl: null,
repoName: null,
};
// Check if Git is installed
const gitInstalled = await git_helper_1.GitHelper.isGitInstalled();
if (!gitInstalled) {
return result;
}
// Validate repository
try {
const validation = await git_helper_1.GitHelper.validateRepository(cwd);
result.isGitRepo = validation.isGitRepo;
result.hasRemote = validation.hasRemote;
if (validation.hasRemote && validation.remoteUrl) {
result.repositoryUrl = validation.remoteUrl;
// Extract repository name and generate baseUrl
result.repoName = deployment_config_1.DeploymentConfigLoader.extractRepoName(validation.remoteUrl);
result.baseUrl = `/${result.repoName}/`;
}
}
catch (error) {
// Silently fail - Git detection is optional during init
}
return result;
}
/**
* Get the template directory path
*/
function getTemplateDirectory() {
// In development (running from src), templates are in src/templates/init
// In production (running from lib), templates are in lib/templates/init
const devPath = path.join(__dirname, '..', 'templates', 'init');
const prodPath = path.join(__dirname, '..', '..', 'lib', 'templates', 'init');
if (fs.existsSync(devPath)) {
return devPath;
}
else if (fs.existsSync(prodPath)) {
return prodPath;
}
else {
// Try relative to the compiled location
const compiledPath = path.join(__dirname, '..', 'templates', 'init');
if (fs.existsSync(compiledPath)) {
return compiledPath;
}
throw new Error('Template directory not found. Please ensure the package is installed correctly.');
}
}
/**
* Copy the projects template file
*/
async function copyProjectsTemplate(templateDir, targetPath, format, minimal) {
const templatePath = path.join(templateDir, 'projects.yaml.template');
if (!fs.existsSync(templatePath)) {
throw new Error(`Template file not found: ${templatePath}`);
}
let content = fs.readFileSync(templatePath, 'utf-8');
if (minimal) {
// Create minimal version with only one project
content = createMinimalProjectsContent(format);
}
else if (format === 'json') {
// Convert YAML template to JSON
content = convertYamlTemplateToJson(content);
}
fs.writeFileSync(targetPath, content, 'utf-8');
logger_1.Logger.success(`Created ${path.basename(targetPath)}`);
}
/**
* Copy the config template file
*/
async function copyConfigTemplate(templateDir, targetPath, gitInfo) {
// Create config object
const config = {
title: "My Projects",
description: "A showcase of my coding projects",
baseUrl: gitInfo.hasRemote && gitInfo.baseUrl ? gitInfo.baseUrl : "./",
dynamicBackgrounds: []
};
// Write as JSON with 2-space indentation
const content = JSON.stringify(config, null, 2);
fs.writeFileSync(targetPath, content, 'utf-8');
logger_1.Logger.success(`Created ${path.basename(targetPath)}`);
}
/**
* Create .gitignore file with common patterns
*/
async function createGitignoreFile(cwd, targetPath) {
const gitignoreContent = `# Projection build output
dist/
# Backup files
.backup
# macOS
.DS_Store
# Node modules (if using npm scripts)
node_modules/
# Editor directories
.vscode/
.idea/
`;
fs.writeFileSync(targetPath, gitignoreContent, 'utf-8');
logger_1.Logger.success('Created .gitignore');
}
/**
* Create README.md file from template
*/
async function createReadmeFile(templateDir, targetPath) {
const templatePath = path.join(templateDir, 'README.md.template');
if (!fs.existsSync(templatePath)) {
throw new Error(`Template file not found: ${templatePath}`);
}
const content = fs.readFileSync(templatePath, 'utf-8');
fs.writeFileSync(targetPath, content, 'utf-8');
logger_1.Logger.success('Created README.md');
}
/**
* Create minimal projects content
*/
function createMinimalProjectsContent(format) {
if (format === 'json') {
return JSON.stringify({
projects: [
{
id: "my-first-project",
title: "My First Project",
description: "Replace this with your own project!",
creationDate: new Date().toISOString().split('T')[0],
tags: ["example"],
pageLink: "https://example.com"
}
]
}, null, 2);
}
else {
const today = new Date().toISOString().split('T')[0];
return `# Projection Project Data
projects:
- id: "my-first-project"
title: "My First Project"
description: "Replace this with your own project!"
creationDate: "${today}"
tags:
- "example"
pageLink: "https://example.com"
`;
}
}
/**
* Convert YAML template to JSON format (basic conversion)
*/
function convertYamlTemplateToJson(yamlContent) {
// For now, we'll use a simple approach
// In a real implementation, we'd parse the YAML and convert to JSON
// But since this is a template with comments, we'll create a clean JSON version
return JSON.stringify({
projects: [
{
id: "example-project",
title: "Example Project",
description: "This is an example project demonstrating all available fields. Replace this with your own projects!",
creationDate: "2024-01-15",
tags: ["web", "javascript", "example"],
pageLink: "https://example.com/project",
sourceLink: "https://github.com/username/example-project",
thumbnailLink: "./images/example-thumbnail.png",
featured: true
},
{
id: "minimal-project",
title: "Minimal Project",
description: "A minimal project with only required fields.",
creationDate: "2024-02-20",
tags: ["example"],
pageLink: "https://example.com/minimal"
}
]
}, null, 2);
}
/**
* Prompt user for input
*/
function promptUser(question) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
rl.question(question, (answer) => {
rl.close();
resolve(answer);
});
});
}
/**
* Display success message with next steps
*/
function displaySuccessMessage(projectsFile, configFile, gitignoreFile, readmeFile, gitInfo) {
logger_1.Logger.newline();
logger_1.Logger.icon('🎉', 'Successfully initialized Projection project!', '\x1b[32m');
logger_1.Logger.newline();
logger_1.Logger.info('Created files:');
logger_1.Logger.list([projectsFile, configFile, gitignoreFile, readmeFile]);
logger_1.Logger.newline();
// Display Git repository information if detected
if (gitInfo.isGitRepo && gitInfo.hasRemote) {
logger_1.Logger.info('Git repository detected:');
logger_1.Logger.list([
`Repository: ${gitInfo.repositoryUrl}`,
`Base URL configured: ${gitInfo.baseUrl}`
]);
logger_1.Logger.newline();
}
else if (gitInfo.isGitRepo && !gitInfo.hasRemote) {
logger_1.Logger.warn('Git repository detected but no remote configured.');
logger_1.Logger.dim('Add a remote to enable GitHub Pages deployment:');
logger_1.Logger.dim(' git remote add origin <repository-url>');
logger_1.Logger.newline();
}
logger_1.Logger.info('Next steps:');
const steps = [
`Run 'projection admin' to manage your projects and see a live preview`,
`Run 'projection dev' to start development server`
];
logger_1.Logger.list(steps);
logger_1.Logger.newline();
logger_1.Logger.dim('📚 Documentation: https://github.com/quasarbright/projection');
logger_1.Logger.newline();
logger_1.Logger.icon('🚀', 'Happy building!', '\x1b[36m');
logger_1.Logger.newline();
}
//# sourceMappingURL=init.js.map