Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/little-gorillas-accept.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@dotgithub/cli": patch
---

Add starter template support to `dotgithub init` (`node-library`, `bun-app`, `monorepo`) and update init command docs.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ dotgithub init
dotgithub init --output ./my-workflows
```

### Add GitHub Actions
### Add GitHub Actions (and generate TypeScript wrappers)

```bash
# Add a specific action
# Add a specific action and generate its TypeScript wrapper/types
dotgithub add actions/checkout@v4

# Add multiple actions
# Add multiple actions and generate wrappers for each
dotgithub add actions/setup-node@v4 actions/setup-python@v5
```

Expand All @@ -53,7 +53,7 @@ dotgithub synth
## Basic Usage

1. **Initialize** your project with `dotgithub init`
2. **Configure** actions in `dotgithub.json`
2. **Add actions** with `dotgithub add ...` (this generates TypeScript action wrappers and updates config)
3. **Write** your workflow logic in TypeScript
4. **Synthesize** workflows with `dotgithub synth`

Expand Down
21 changes: 17 additions & 4 deletions docs/command-init.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ The `init` command sets up a new DotGitHub project by creating the necessary dir
## Options

- `--force` - Overwrite existing files if they exist
- `--output <dir>` - Output directory for the workspace (default: `src`)
- `--output <dir>` - Output directory for the workspace (default: `.github`)
- `--template <name>` - Starter template (`node-library`, `bun-app`, `monorepo`)

## What it creates

Expand All @@ -35,15 +36,27 @@ The `init` command creates the following files and directories:
dotgithub init
```

This creates a `src/` directory with all necessary files.
This creates a `.github/` workspace with all necessary files.

### Custom output directory

```bash
dotgithub init --output ./my-workflows
```

This creates a `my-workflows/` directory instead of `src/`.
This creates a `my-workflows/` directory instead of `.github/`.

### Choose a starter template

```bash
dotgithub init --template bun-app
```

Available templates:

- `node-library` (default)
- `bun-app`
- `monorepo`

### Force overwrite existing files

Expand Down Expand Up @@ -89,7 +102,7 @@ A basic entry point that imports the DotGitHub core library.

After running `init`, you typically:

1. Navigate to the workspace directory: `cd src`
1. Navigate to the workspace directory: `cd .github/src`
2. Install dependencies: `npm install`
3. Add GitHub Actions: `dotgithub add actions/checkout@v4`
4. Write your workflow logic in TypeScript
Expand Down
119 changes: 102 additions & 17 deletions packages/cli/src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,38 @@ import { Command } from 'commander';
import * as fs from 'fs';
import * as path from 'path';
import {
createConfigFile,
writeConfig,
createDefaultConfig,
type DotGithubContext,
logger,
} from '@dotgithub/core';

const SUPPORTED_TEMPLATES = ['node-library', 'bun-app', 'monorepo'] as const;
type StarterTemplate = (typeof SUPPORTED_TEMPLATES)[number];

export interface InitCommandOptions {
force?: boolean;
output?: string;
template?: StarterTemplate;
}

export function createInitCommand(
createContext: (options?: any) => DotGithubContext
): Command {
return new Command('init')
.description(
'Initialize a new GitHub Actions workspace with TypeScript and ESM support'
'Initialize a new GitHub Actions TypeScript workspace with synth-ready starter templates'
)
.option('--force', 'Overwrite existing files if they exist', false)
.option(
'--output <dir>',
'Output directory for the workspace (default: .github)',
'.github'
)
.option(
'--template <name>',
`Starter template: ${SUPPORTED_TEMPLATES.join(', ')}`,
'node-library'
)
.action(async (options: InitCommandOptions) => {
try {
await initializeWorkspace(options, createContext);
Expand All @@ -48,6 +55,8 @@ async function initializeWorkspace(
const outputDir = options.output || '.github';
const outputDirPath = path.resolve(outputDir);

const template = normalizeTemplate(options.template);

// Create output directory if it doesn't exist
if (!fs.existsSync(outputDirPath)) {
fs.mkdirSync(outputDirPath, { recursive: true });
Expand All @@ -65,9 +74,9 @@ async function initializeWorkspace(

// Create default config with workspace output directory
const defaultConfig = createDefaultConfig();
defaultConfig.rootDir = 'src'; // This will be the workspace directory inside the output directory
defaultConfig.outputDir = '.'; // Set output directory to current directory
defaultConfig.rootDir = 'src'; // Workspace directory inside the output directory
defaultConfig.outputDir = '.';

// Add a local construct definition
defaultConfig.constructs = [
{
Expand Down Expand Up @@ -113,7 +122,7 @@ async function initializeWorkspace(
}

// Generate package.json
const packageJson = generatePackageJson();
const packageJson = generatePackageJson(template);
fs.writeFileSync(
packageJsonPath,
JSON.stringify(packageJson, null, 2) + '\n'
Expand All @@ -124,17 +133,18 @@ async function initializeWorkspace(
if (fs.existsSync(tsconfigPath) && !options.force) {
logger.info('✓ tsconfig.json already exists, skipping');
} else {
const tsconfig = generateTsConfig();
const tsconfig = generateTsConfig(template);
fs.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2) + '\n');
}

// Create basic index.ts file
const indexPath = path.join(workspaceDir, 'index.ts');
if (!fs.existsSync(indexPath) || options.force) {
const indexContent = generateIndexFile();
const indexContent = generateIndexFile(template);
fs.writeFileSync(indexPath, indexContent);
}

logger.info(`Template: ${template}`);
logger.info('Generated files:');
logger.debug(` ${outputDir}/dotgithub.json`);
logger.debug(` ${workspaceDir}/package.json`);
Expand All @@ -145,11 +155,74 @@ async function initializeWorkspace(
logger.info(' npm install');
}

function generatePackageJson(): object {
function normalizeTemplate(value?: string): StarterTemplate {
const candidate = (value || 'node-library') as StarterTemplate;
if (SUPPORTED_TEMPLATES.includes(candidate)) {
return candidate;
}
throw new Error(
`Unsupported template "${value}". Valid templates: ${SUPPORTED_TEMPLATES.join(', ')}`
);
}

function generatePackageJson(template: StarterTemplate): object {
if (template === 'bun-app') {
return {
name: 'github-actions-bun-workspace',
version: '1.0.0',
description: 'DotGitHub Bun app workspace with TypeScript support',
type: 'module',
main: 'dist/index.js',
scripts: {
build: 'tsc',
dev: 'bun --watch src/index.ts',
clean: 'rm -rf dist',
},
dependencies: {
'@dotgithub/core': '*',
'@dotgithub/cli': '*',
},
devDependencies: {
'@types/node': '^20.0.0',
typescript: '^5.0.0',
},
engines: {
bun: '>=1.2.22',
},
packageManager: 'bun@1.2.22',
};
}

if (template === 'monorepo') {
return {
name: 'github-actions-monorepo-workspace',
version: '1.0.0',
private: true,
description: 'DotGitHub monorepo starter with TypeScript support',
type: 'module',
scripts: {
build: 'tsc',
clean: 'rm -rf dist',
},
workspaces: ['packages/*'],
dependencies: {
'@dotgithub/core': '*',
'@dotgithub/cli': '*',
},
devDependencies: {
'@types/node': '^20.0.0',
typescript: '^5.0.0',
},
engines: {
node: '>=18.0.0',
},
};
}

return {
name: 'github-actions-workspace',
version: '1.0.0',
description: 'GitHub Actions workspace with TypeScript support',
description: 'DotGitHub Node library workspace with TypeScript support',
type: 'module',
main: 'dist/index.js',
module: 'dist/index.js',
Expand All @@ -173,12 +246,13 @@ function generatePackageJson(): object {
};
}

function generateTsConfig(): object {
function generateTsConfig(template: StarterTemplate): object {
const moduleResolution = template === 'bun-app' ? 'bundler' : 'bundler';
return {
compilerOptions: {
target: 'ES2022',
module: 'ESNext',
moduleResolution: 'bundler',
moduleResolution,
allowSyntheticDefaultImports: true,
esModuleInterop: true,
forceConsistentCasingInFileNames: true,
Expand All @@ -197,9 +271,20 @@ function generateTsConfig(): object {
};
}

function generateIndexFile(): string {
return `// GitHub Actions workspace entry point
import {
function generateIndexFile(template: StarterTemplate): string {
if (template === 'bun-app') {
return `// Bun-oriented DotGitHub starter entry point\n// Use this to author CI/CD workflow constructs in TypeScript and synthesize YAML.\n\n${baseIndexContent()}`;
}

if (template === 'monorepo') {
return `// Monorepo-oriented DotGitHub starter entry point\n// Tip: split constructs by package and export a composed default construct.\n\n${baseIndexContent()}`;
}

return `// Node library-oriented DotGitHub starter entry point\n// Author workflow logic in TypeScript and synthesize to GitHub Actions YAML.\n\n${baseIndexContent()}`;
}

function baseIndexContent(): string {
return `import {
GitHubConstruct,
GitHubStack,
JobConstruct,
Expand Down Expand Up @@ -279,7 +364,7 @@ export class MyConstruct extends GitHubConstruct {
});

// Create a job
const job = new JobConstruct(workflow, 'test', {
new JobConstruct(workflow, 'test', {
name: 'Test',
'runs-on': 'ubuntu-latest',
steps: [
Expand Down