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
20 changes: 10 additions & 10 deletions packages/create-gen-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ A TypeScript-first CLI/library for cloning template repositories, asking the use
## Features

- Clone any Git repo (or GitHub `org/repo` shorthand) and optionally select a branch + subdirectory
- Extract template variables from filenames and file contents using the safer `____VARIABLE____` convention
- Extract template variables from filenames and file contents using the safer `____variable____` convention
- Merge auto-discovered variables with `.questions.{json,js}` (questions win, including `ignore` patterns)
- Interactive prompts powered by `inquirerer`, with CLI flag overrides (`--VAR value`) and non-TTY mode for CI
- Built-in CLI (`create-gen-app` / `cga`) that discovers templates, prompts once, and writes output safely
Expand Down Expand Up @@ -84,14 +84,14 @@ await createGen({
Variables should be wrapped in four underscores on each side:

```
____PROJECT_NAME____/
src/____MODULE_NAME____.ts
____projectName____/
src/____moduleName____.ts
```

```typescript
// ____MODULE_NAME____.ts
export const projectName = "____PROJECT_NAME____";
export const author = "____USERFULLNAME____";
// ____moduleName____.ts
export const projectName = "____projectName____";
export const author = "____fullName____";
```

### Custom Questions & Ignore Rules
Expand All @@ -103,13 +103,13 @@ Create a `.questions.json`:
"ignore": ["__tests__", "docs/drafts"],
"questions": [
{
"name": "____USERFULLNAME____",
"name": "____fullName____",
"type": "text",
"message": "Enter author full name",
"required": true
},
{
"name": "____LICENSE____",
"name": "____license____",
"type": "list",
"message": "Choose a license",
"options": ["MIT", "Apache-2.0", "ISC", "GPL-3.0"]
Expand All @@ -118,7 +118,7 @@ Create a `.questions.json`:
}
```

Or `.questions.js` for dynamic logic. Question names can use `____VAR____` or plain `VAR`; they'll be normalized automatically.
Or `.questions.js` for dynamic logic. Question names can use `____var____` or plain `VAR`; they'll be normalized automatically.

### License Templates

Expand All @@ -136,4 +136,4 @@ No code changes are needed; the CLI discovers templates at runtime and will warn
- `promptUser(extracted, argv, noTty)` – run interactive questions with CLI overrides and alias deduping
- `replaceVariables(templateDir, outputDir, extracted, answers)` – copy files, rename paths, render licenses

See `dev/README.md` for the local development helper script (`pnpm dev`).
See `dev/README.md` for the local development helper script (`pnpm dev`).
42 changes: 21 additions & 21 deletions packages/create-gen-app/__tests__/create-gen.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ describe("create-gen-app", () => {
describe("extractVariables", () => {
it("should extract variables from filenames", async () => {
fs.writeFileSync(
path.join(testTempDir, "____PROJECT_NAME____.txt"),
path.join(testTempDir, "____projectName____.txt"),
"content"
);
fs.writeFileSync(path.join(testTempDir, "____AUTHOR____.md"), "content");
fs.writeFileSync(path.join(testTempDir, "____author____.md"), "content");

const result = await extractVariables(testTempDir);

Expand All @@ -62,7 +62,7 @@ describe("create-gen-app", () => {
it("should extract variables from file contents", async () => {
fs.writeFileSync(
path.join(testTempDir, "test.txt"),
"Hello ____USER_NAME____, welcome to ____PROJECT_NAME____!"
"Hello ____userName____, welcome to ____projectName____!"
);

const result = await extractVariables(testTempDir);
Expand All @@ -77,11 +77,11 @@ describe("create-gen-app", () => {
});

it("should extract variables from nested directories", async () => {
const nestedDir = path.join(testTempDir, "src", "____MODULE_NAME____");
const nestedDir = path.join(testTempDir, "src", "____moduleName____");
fs.mkdirSync(nestedDir, { recursive: true });
fs.writeFileSync(
path.join(nestedDir, "____FILE_NAME____.ts"),
'export const ____CONSTANT____ = "value";'
path.join(nestedDir, "____fileName____.ts"),
'export const ____constant____ = "value";'
);

const result = await extractVariables(testTempDir);
Expand Down Expand Up @@ -163,7 +163,7 @@ module.exports = {
it("should skip .questions.json and .questions.js from variable extraction", async () => {
fs.writeFileSync(
path.join(testTempDir, ".questions.json"),
'{"questions": [{"name": "____SHOULD_NOT_EXTRACT____"}]}'
'{"questions": [{"name": "____shouldNotExtract____"}]}'
);

const result = await extractVariables(testTempDir);
Expand All @@ -176,7 +176,7 @@ module.exports = {
it("should handle variables with different casings", async () => {
fs.writeFileSync(
path.join(testTempDir, "test.txt"),
"____lowercase____ ____UPPERCASE____ ____CamelCase____ ____snake_case____"
"____lowercase____ ____uppercase____ ____CamelCase____ ____snake_case____"
);

const result = await extractVariables(testTempDir);
Expand Down Expand Up @@ -210,9 +210,9 @@ module.exports = {

const extractedVariables: ExtractedVariables = {
fileReplacers: [
{ variable: "PROJECT_NAME", pattern: /____PROJECT_NAME____/g },
{ variable: "projectName", pattern: /____projectName____/g },
],
contentReplacers: [{ variable: "AUTHOR", pattern: /____AUTHOR____/g }],
contentReplacers: [{ variable: "author", pattern: /____author____/g }],
projectQuestions: null,
};

Expand Down Expand Up @@ -272,7 +272,7 @@ module.exports = {

const extractedVariables: ExtractedVariables = {
fileReplacers: [
{ variable: "PROJECT_NAME", pattern: /____PROJECT_NAME____/g },
{ variable: "projectName", pattern: /____projectName____/g },
],
contentReplacers: [],
projectQuestions: null,
Expand All @@ -298,7 +298,7 @@ module.exports = {
const extractedVariables: ExtractedVariables = {
fileReplacers: [],
contentReplacers: [
{ variable: "USERFULLNAME", pattern: /____USERFULLNAME____/g },
{ variable: "fullName", pattern: /____fullName____/g },
],
projectQuestions: {
questions: [
Expand Down Expand Up @@ -330,7 +330,7 @@ module.exports = {
const extractedVariables: ExtractedVariables = {
fileReplacers: [],
contentReplacers: [
{ variable: "MODULEDESC", pattern: /____MODULEDESC____/g },
{ variable: "moduleDesc", pattern: /____moduleDesc____/g },
],
projectQuestions: {
questions: [
Expand Down Expand Up @@ -363,7 +363,7 @@ module.exports = {
const extractedVariables: ExtractedVariables = {
fileReplacers: [],
contentReplacers: [
{ variable: "USERFULLNAME", pattern: /____USERFULLNAME____/g },
{ variable: "fullName", pattern: /____fullName____/g },
],
projectQuestions: {
questions: [
Expand Down Expand Up @@ -394,7 +394,7 @@ module.exports = {
const extractedVariables: ExtractedVariables = {
fileReplacers: [],
contentReplacers: [
{ variable: "MODULEDESC", pattern: /____MODULEDESC____/g },
{ variable: "moduleDesc", pattern: /____moduleDesc____/g },
],
projectQuestions: {
questions: [
Expand All @@ -417,7 +417,7 @@ module.exports = {
it("should replace variables in file contents", async () => {
fs.writeFileSync(
path.join(testTempDir, "README.md"),
"# ____PROJECT_NAME____\n\nBy ____AUTHOR____"
"# ____projectName____\n\nBy ____author____"
);

const extractedVariables = await extractVariables(testTempDir);
Expand All @@ -443,7 +443,7 @@ module.exports = {

it("should replace variables in filenames", async () => {
fs.writeFileSync(
path.join(testTempDir, "____PROJECT_NAME____.config.js"),
path.join(testTempDir, "____projectName____.config.js"),
"module.exports = {};"
);

Expand All @@ -466,11 +466,11 @@ module.exports = {
});

it("should replace variables in nested directory names", async () => {
const nestedDir = path.join(testTempDir, "src", "____MODULE_NAME____");
const nestedDir = path.join(testTempDir, "src", "____moduleName____");
fs.mkdirSync(nestedDir, { recursive: true });
fs.writeFileSync(
path.join(nestedDir, "index.ts"),
'export const name = "____MODULE_NAME____";'
'export const name = "____moduleName____";'
);

const extractedVariables = await extractVariables(testTempDir);
Expand Down Expand Up @@ -517,7 +517,7 @@ module.exports = {
it("should handle multiple occurrences of the same variable", async () => {
fs.writeFileSync(
path.join(testTempDir, "test.txt"),
"____NAME____ loves ____NAME____ and ____NAME____ is great!"
"____name____ loves ____name____ and ____name____ is great!"
);

const extractedVariables = await extractVariables(testTempDir);
Expand Down Expand Up @@ -578,7 +578,7 @@ module.exports = {
fs.mkdirSync(ignoredDir);
fs.writeFileSync(
path.join(ignoredDir, "example.txt"),
"This file has ____IGNORED____ variable"
"This file has ____ignored____ variable"
);
fs.writeFileSync(
path.join(testTempDir, ".questions.json"),
Expand Down
2 changes: 1 addition & 1 deletion packages/create-gen-app/src/extract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
const PLACEHOLDER_BOUNDARY = "____";

/**
* Pattern to match ____VARIABLE____ in filenames and content
* Pattern to match ____variable____ in filenames and content
*/
const VARIABLE_PATTERN = new RegExp(
`${PLACEHOLDER_BOUNDARY}([A-Za-z_][A-Za-z0-9_]*)${PLACEHOLDER_BOUNDARY}`,
Expand Down
4 changes: 2 additions & 2 deletions packages/create-gen-app/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ export interface Questions {
}

/**
* Variable extracted from filename patterns like ____VARIABLE____
* Variable extracted from filename patterns like ____variable____
*/
export interface FileReplacer {
variable: string;
pattern: RegExp;
}

/**
* Variable extracted from file content patterns like ____VARIABLE____
* Variable extracted from file content patterns like ____variable____
*/
export interface ContentReplacer {
variable: string;
Expand Down
Loading