@jml6m/skeletor 0.1.0-alpha.1 → 0.1.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +140 -7
- package/package.json +33 -4
- package/src/index.js +316 -5
- package/templates/csharp/AGENTS.md +10 -0
- package/templates/csharp/Program.cs +18 -0
- package/templates/csharp/ProgramTests.cs +12 -0
- package/templates/csharp/README.md +12 -0
- package/templates/csharp/template.json +10 -0
- package/templates/csharp/{{PROJECT_NAME}}.csproj +17 -0
- package/templates/go/AGENTS.md +9 -0
- package/templates/go/README.md +13 -0
- package/templates/go/go.mod +5 -0
- package/templates/go/main.go +20 -0
- package/templates/go/main_test.go +11 -0
- package/templates/go/template.json +11 -0
- package/templates/java/AGENTS.md +9 -0
- package/templates/java/README.md +12 -0
- package/templates/java/pom.xml +38 -0
- package/templates/java/src/main/java/com/example/App.java +16 -0
- package/templates/java/src/test/java/com/example/AppTest.java +12 -0
- package/templates/java/template.json +10 -0
- package/templates/javascript/.editorconfig +16 -0
- package/templates/javascript/.eslintrc.json +82 -0
- package/templates/javascript/.github/workflows/README.md +7 -0
- package/templates/javascript/.github/workflows/ci.yml +39 -0
- package/templates/javascript/.husky/pre-commit +4 -0
- package/templates/javascript/.prettierrc.json +24 -0
- package/templates/javascript/AGENTS.md +110 -0
- package/templates/javascript/README.md +38 -0
- package/templates/javascript/docs/CODING_STANDARDS.md +14 -0
- package/templates/javascript/jest.config.js +7 -0
- package/templates/javascript/jsconfig.json +19 -0
- package/templates/javascript/knip.json +7 -0
- package/templates/javascript/package.json +60 -0
- package/templates/javascript/release.js +145 -0
- package/templates/javascript/scripts/generate-context.js +73 -0
- package/templates/javascript/scripts/reinstall.js +25 -0
- package/templates/javascript/src/config/env.config.js +25 -0
- package/templates/javascript/src/config/index.js +7 -0
- package/templates/javascript/src/constants/index.js +11 -0
- package/templates/javascript/src/index.js +19 -0
- package/templates/javascript/src/utils/logger.js +42 -0
- package/templates/javascript/template.json +13 -0
- package/templates/javascript/tests/example.test.js +10 -0
- package/templates/python/AGENTS.md +10 -0
- package/templates/python/README.md +16 -0
- package/templates/python/pyproject.toml +45 -0
- package/templates/python/src/app/__init__.py +7 -0
- package/templates/python/src/app/main.py +8 -0
- package/templates/python/template.json +12 -0
- package/templates/python/tests/test_main.py +9 -0
- package/templates/rust/AGENTS.md +9 -0
- package/templates/rust/Cargo.toml +10 -0
- package/templates/rust/README.md +12 -0
- package/templates/rust/src/main.rs +22 -0
- package/templates/rust/template.json +10 -0
- package/templates/typescript/.editorconfig +16 -0
- package/templates/typescript/.github/workflows/README.md +7 -0
- package/templates/typescript/.github/workflows/ci.yml +39 -0
- package/templates/typescript/.husky/pre-commit +4 -0
- package/templates/typescript/.prettierrc.json +24 -0
- package/templates/typescript/AGENTS.md +110 -0
- package/templates/typescript/README.md +38 -0
- package/templates/typescript/docs/CODING_STANDARDS.md +14 -0
- package/templates/typescript/eslint.config.js +55 -0
- package/templates/typescript/jest.config.js +7 -0
- package/templates/typescript/knip.json +7 -0
- package/templates/typescript/package.json +57 -0
- package/templates/typescript/release.js +145 -0
- package/templates/typescript/scripts/generate-context.js +73 -0
- package/templates/typescript/scripts/reinstall.js +25 -0
- package/templates/typescript/src/config/env.config.ts +25 -0
- package/templates/typescript/src/config/index.ts +7 -0
- package/templates/typescript/src/constants/index.ts +11 -0
- package/templates/typescript/src/index.ts +16 -0
- package/templates/typescript/src/utils/logger.ts +42 -0
- package/templates/typescript/template.json +14 -0
- package/templates/typescript/tests/example.test.js +10 -0
- package/templates/typescript/tsconfig.json +21 -0
package/README.md
CHANGED
|
@@ -1,11 +1,144 @@
|
|
|
1
1
|
# Skeletor
|
|
2
|
-
Efficient scaffolding for custom development
|
|
2
|
+
Efficient scaffolding for custom development.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
Generates empty (or lightly stubbed) projects pre-configured with **your** rules and recommendations, derived from analysis of many GitHub-linked workspaces (local-land-client/server, cuda-sandbox, x-app-skeleton, etc.).
|
|
5
|
+
|
|
6
|
+
## Available Templates
|
|
7
|
+
|
|
8
|
+
Skeletor lets you **pick** the kind of project you want. Run `skeletor new my-project` (omit `--template`) for an interactive selector, or pass `--template <id>`.
|
|
9
|
+
|
|
10
|
+
Current templates (standard main libraries + your flavors where they existed):
|
|
11
|
+
|
|
12
|
+
- `javascript` — Your exact CJS conventions (Prettier 165, ESLint+unused-imports, aliases, health tools, release gates, AGENTS.md, husky+lint-staged, etc.)
|
|
13
|
+
- `typescript` — Modern ESM + flat ESLint + TypeScript, tsconfig, build, your quality gates + AGENTS.md
|
|
14
|
+
- `python` — Best-practice defaults (pyproject.toml + ruff for lint+format, pytest, mypy, src layout)
|
|
15
|
+
- `go` — Standard Go module (`go.mod` + main + tests)
|
|
16
|
+
- `rust` — Standard Cargo binary crate
|
|
17
|
+
- `java` — Maven + JUnit 5 structure
|
|
18
|
+
- `csharp` — .NET 8 console + xUnit
|
|
19
|
+
|
|
20
|
+
Each template includes a `template.json` manifest that declares `verifyCommands` — the exact steps (3 & 4) a developer should run after scaffolding. The test suite discovers templates and validates this contract.
|
|
21
|
+
|
|
22
|
+
> New languages/stacks: Drop a directory under `templates/<id>/` with a `template.json` + conventional source/build/test files using `{{PROJECT_NAME}}` etc. tokens. The CLI, listing, and tests pick it up automatically.
|
|
23
|
+
|
|
24
|
+
## Rich Interactive CLI
|
|
25
|
+
|
|
26
|
+
We use `@clack/prompts` for a modern experience:
|
|
27
|
+
- Beautiful template selector with descriptions/hints when you omit `--template`
|
|
28
|
+
- Prompts for owner and description (when running interactively without `--yes`)
|
|
29
|
+
- Confirmation step
|
|
30
|
+
- Proper cancel handling and nice spinners/intro/outro
|
|
31
|
+
|
|
32
|
+
`--yes` stays fully non-interactive for scripts/CI.
|
|
33
|
+
|
|
34
|
+
## Pushing to GitHub
|
|
35
|
+
|
|
36
|
+
**Pushing is not automatic.** After every set of changes (new templates, CLI updates, tests, docs), you **must** explicitly:
|
|
37
|
+
1. `git add -A`
|
|
38
|
+
2. `git commit -m "..."` (good message)
|
|
39
|
+
3. `git push origin main`
|
|
40
|
+
|
|
41
|
+
See the root `AGENTS.md` for the standing instruction given to agents working on this repo. This ensures the GitHub repo (`https://github.com/jml6m/skeletor`) stays in sync with every request. There is no special Grok Build setting that auto-pushes; it must be done via shell commands in the agent loop (or documented in project instructions like AGENTS.md).
|
|
42
|
+
|
|
43
|
+
## Usage (alpha)
|
|
6
44
|
|
|
7
|
-
## Installation
|
|
8
|
-
You can run the alpha placeholder using npx:
|
|
9
45
|
```bash
|
|
10
|
-
|
|
11
|
-
|
|
46
|
+
# Interactive (recommended for exploration)
|
|
47
|
+
node src/index.js new my-project
|
|
48
|
+
|
|
49
|
+
# Or specify
|
|
50
|
+
node src/index.js new my-api --template typescript --owner jml6m
|
|
51
|
+
|
|
52
|
+
# Non-interactive (CI / scripts)
|
|
53
|
+
npx @jml6m/skeletor new my-service --yes --template go
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Common flags:
|
|
57
|
+
- `--template <id>` (javascript, typescript, python, go, rust, java, csharp, ...)
|
|
58
|
+
- `--owner`
|
|
59
|
+
- `--description`
|
|
60
|
+
- `--yes` (skip all prompts)
|
|
61
|
+
- `--no-git`
|
|
62
|
+
|
|
63
|
+
After scaffolding, follow the `verifyCommands` from that template's `template.json` (shown in the success message). E.g. for most JS/TS: `npm install && npm run health:full` etc.
|
|
64
|
+
|
|
65
|
+
Run `skeletor` (or `node src/index.js`) with no args to see current templates.
|
|
66
|
+
|
|
67
|
+
## Philosophy / Goals
|
|
68
|
+
|
|
69
|
+
- Developers **pick** the scaffolding they want (language + flavor).
|
|
70
|
+
- Your personal conventions (from the workspaces analysis) are encoded in the relevant templates (javascript/typescript for now).
|
|
71
|
+
- Everything else starts from clean "standard main library" best practices and can be refined later.
|
|
72
|
+
- The `template.json` manifest per template is the contract for post-generation steps (this is what the test suite validates as steps 3 & 4).
|
|
73
|
+
- Easy to evolve: add/edit under `templates/<id>/`. New templates are auto-discovered by the CLI and tests.
|
|
74
|
+
|
|
75
|
+
## Status
|
|
76
|
+
|
|
77
|
+
Active. Multiple language templates + rich clack-based interactive UI. Test suite validates generation + manifest contracts for every template.
|
|
78
|
+
|
|
79
|
+
Run `skeletor` (or `node src/index.js`) with no args for current help and template list.
|
|
80
|
+
|
|
81
|
+
## For Testers & Reviewers
|
|
82
|
+
|
|
83
|
+
### Quick try (no install needed)
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# From a clone of this repo
|
|
87
|
+
node src/index.js new my-test-project --yes
|
|
88
|
+
|
|
89
|
+
cd my-test-project
|
|
90
|
+
npm install
|
|
91
|
+
npm run format
|
|
92
|
+
npm run lint
|
|
93
|
+
npm test
|
|
94
|
+
npm run health:full
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Inspect the generated files, especially:
|
|
98
|
+
- `AGENTS.md`
|
|
99
|
+
- `release.js` (owner/repo substituted)
|
|
100
|
+
- `.prettierrc.json`, `.eslintrc.json`, `knip.json`
|
|
101
|
+
- `.husky/pre-commit` and `lint-staged` config
|
|
102
|
+
- `.github/workflows/ci.yml`
|
|
103
|
+
- `src/` structure + alias usage in `src/index.js`
|
|
104
|
+
- `package.json` scripts and `_moduleAliases`
|
|
105
|
+
|
|
106
|
+
### Running the unit + integration test suite
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
npm install # pulls jest (devDep)
|
|
110
|
+
npm test
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The tests cover:
|
|
114
|
+
- CLI argument parsing
|
|
115
|
+
- Template token rendering (`{{PROJECT_NAME}}` etc.)
|
|
116
|
+
- Full generation into temp dirs (verifies file presence, JSON validity, token substitution, new husky/github artifacts, refusal to overwrite, etc.)
|
|
117
|
+
- The generated projects are exercised for basic structure (no full `npm install` of heavy tools in every test to keep it fast).
|
|
118
|
+
|
|
119
|
+
All tests are in `tests/`.
|
|
120
|
+
|
|
121
|
+
### Updating / Adding Templates
|
|
122
|
+
|
|
123
|
+
The source of truth lives in `templates/<id>/` (each with its own `template.json`).
|
|
124
|
+
|
|
125
|
+
- Edit files inside an existing template → next generation gets the change.
|
|
126
|
+
- Add a whole new `templates/newlang/` with `template.json` + files → it appears in the picker, help, and tests automatically.
|
|
127
|
+
|
|
128
|
+
Always run `npm test` after template work.
|
|
129
|
+
|
|
130
|
+
When happy, commit + push (see root `AGENTS.md` for the standing rule).
|
|
131
|
+
|
|
132
|
+
### GitHub push & external review
|
|
133
|
+
|
|
134
|
+
Pushing is **explicit** (not automatic). After changes:
|
|
135
|
+
- Run tests
|
|
136
|
+
- `git add -A && git commit -m "..." && git push origin main`
|
|
137
|
+
|
|
138
|
+
See the root `AGENTS.md` (the instruction file for agents) for the exact rule.
|
|
139
|
+
|
|
140
|
+
The repo lives at https://github.com/jml6m/skeletor.
|
|
141
|
+
|
|
142
|
+
Open issues/PRs with feedback on new templates, UI, etc.
|
|
143
|
+
|
|
144
|
+
Now, finish the UI polish if needed and do the push.
|
package/package.json
CHANGED
|
@@ -1,13 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jml6m/skeletor",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
4
|
-
"description": "Efficient scaffolding for custom development",
|
|
3
|
+
"version": "0.1.0-alpha.2",
|
|
4
|
+
"description": "Efficient scaffolding for custom development. Generates projects wired with personal lint/format/health/release/alias/AGENTS.md conventions.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
8
|
"skeletor": "src/index.js"
|
|
9
9
|
},
|
|
10
|
-
"
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js",
|
|
12
|
+
"test:watch": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --watch"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"src",
|
|
16
|
+
"templates",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"keywords": [
|
|
20
|
+
"scaffolding",
|
|
21
|
+
"generator",
|
|
22
|
+
"yeoman",
|
|
23
|
+
"template",
|
|
24
|
+
"eslint",
|
|
25
|
+
"prettier",
|
|
26
|
+
"knip"
|
|
27
|
+
],
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@clack/prompts": "^0.9.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"jest": "^29.7.0"
|
|
33
|
+
},
|
|
11
34
|
"author": "",
|
|
12
|
-
"license": "ISC"
|
|
35
|
+
"license": "ISC",
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=20.19.0"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@clack/prompts": "^1.5.1"
|
|
41
|
+
}
|
|
13
42
|
}
|
package/src/index.js
CHANGED
|
@@ -1,7 +1,318 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Skeletor — Efficient scaffolding for custom development
|
|
4
|
+
* Generates projects pre-configured with personal standards derived from
|
|
5
|
+
* local-land-*, cuda-sandbox, x-app-skeleton and similar workspaces.
|
|
6
|
+
*/
|
|
2
7
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
import { execSync } from 'child_process';
|
|
12
|
+
import * as p from '@clack/prompts';
|
|
13
|
+
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const __dirname = path.dirname(__filename);
|
|
16
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
17
|
+
const TEMPLATES_DIR = path.join(ROOT, 'templates');
|
|
18
|
+
|
|
19
|
+
const USAGE = `
|
|
20
|
+
💀 skeletor — pick your scaffolding
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
skeletor new <name> [options]
|
|
24
|
+
skeletor --help
|
|
25
|
+
|
|
26
|
+
Options:
|
|
27
|
+
--template <name> Template to use (e.g. javascript, typescript, python)
|
|
28
|
+
Omit to choose interactively (or use --yes for default)
|
|
29
|
+
--owner <user> GitHub owner/org for release scripts (default: jml6m)
|
|
30
|
+
--description <text> Short project description
|
|
31
|
+
--yes Non-interactive, use defaults + first available template
|
|
32
|
+
--no-git Skip git init
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
skeletor new my-api --template typescript
|
|
36
|
+
skeletor new my-lib --yes
|
|
37
|
+
skeletor new my-tool
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
function log(msg) { console.log(msg); }
|
|
42
|
+
function logError(msg) { console.error(msg); }
|
|
43
|
+
|
|
44
|
+
function parseArgs(argv) {
|
|
45
|
+
const args = argv.slice(2);
|
|
46
|
+
const result = {
|
|
47
|
+
command: null,
|
|
48
|
+
name: null,
|
|
49
|
+
template: null, // resolved later, can be interactive
|
|
50
|
+
owner: 'jml6m',
|
|
51
|
+
description: 'A new project scaffolded with skeletor.',
|
|
52
|
+
yes: false,
|
|
53
|
+
git: true,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
|
57
|
+
result.command = 'help';
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (args[0] === 'new') {
|
|
62
|
+
result.command = 'new';
|
|
63
|
+
result.name = args[1];
|
|
64
|
+
for (let i = 2; i < args.length; i++) {
|
|
65
|
+
const a = args[i];
|
|
66
|
+
if (a === '--template' || a === '-t') result.template = args[++i] || null;
|
|
67
|
+
else if (a === '--owner' || a === '-o') result.owner = args[++i] || 'jml6m';
|
|
68
|
+
else if (a === '--description' || a === '-d') result.description = args[++i] || result.description;
|
|
69
|
+
else if (a === '--yes' || a === '-y') result.yes = true;
|
|
70
|
+
else if (a === '--no-git') result.git = false;
|
|
71
|
+
else if (!a.startsWith('-') && !result.name) result.name = a;
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
result.command = 'help';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function render(content, vars) {
|
|
81
|
+
let out = content;
|
|
82
|
+
for (const [k, v] of Object.entries(vars)) {
|
|
83
|
+
const token = new RegExp(`\\{\\{${k}\\}\\}`, 'g');
|
|
84
|
+
out = out.replace(token, String(v));
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function ensureDir(dir) {
|
|
90
|
+
if (!fs.existsSync(dir)) {
|
|
91
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function copyAndRender(src, dest, vars) {
|
|
96
|
+
const stat = fs.statSync(src);
|
|
97
|
+
if (stat.isDirectory()) {
|
|
98
|
+
ensureDir(dest);
|
|
99
|
+
for (const entry of fs.readdirSync(src)) {
|
|
100
|
+
// never copy node_modules or .git inside templates
|
|
101
|
+
if (entry === 'node_modules' || entry === '.git') continue;
|
|
102
|
+
copyAndRender(path.join(src, entry), path.join(dest, entry), vars);
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// text file -> render
|
|
108
|
+
let content = fs.readFileSync(src, 'utf8');
|
|
109
|
+
content = render(content, vars);
|
|
110
|
+
ensureDir(path.dirname(dest));
|
|
111
|
+
fs.writeFileSync(dest, content, 'utf8');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function getAvailableTemplates() {
|
|
115
|
+
if (!fs.existsSync(TEMPLATES_DIR)) return [];
|
|
116
|
+
return fs.readdirSync(TEMPLATES_DIR).filter((d) => {
|
|
117
|
+
const p = path.join(TEMPLATES_DIR, d);
|
|
118
|
+
return fs.statSync(p).isDirectory();
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function loadTemplateManifest(templateId) {
|
|
123
|
+
const dir = path.join(TEMPLATES_DIR, templateId);
|
|
124
|
+
const manifestPath = path.join(dir, 'template.json');
|
|
125
|
+
let manifest = {
|
|
126
|
+
id: templateId,
|
|
127
|
+
name: templateId,
|
|
128
|
+
description: 'A custom template.',
|
|
129
|
+
language: templateId,
|
|
130
|
+
verifyCommands: [],
|
|
131
|
+
};
|
|
132
|
+
if (fs.existsSync(manifestPath)) {
|
|
133
|
+
try {
|
|
134
|
+
const raw = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
135
|
+
manifest = { ...manifest, ...raw };
|
|
136
|
+
} catch (e) {
|
|
137
|
+
// ignore bad manifest, fall back to defaults
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return { ...manifest, dir };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function getTemplatesWithManifests() {
|
|
144
|
+
const ids = getAvailableTemplates();
|
|
145
|
+
return ids.map((id) => loadTemplateManifest(id));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function chooseTemplateInteractively(templates, isYes) {
|
|
149
|
+
if (isYes || templates.length === 0) {
|
|
150
|
+
return templates[0] ? templates[0].id : null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!process.stdout.isTTY) {
|
|
154
|
+
return templates[0] ? templates[0].id : null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
p.intro('💀 Skeletor — pick your scaffolding');
|
|
158
|
+
|
|
159
|
+
const options = templates.map((t) => ({
|
|
160
|
+
value: t.id,
|
|
161
|
+
label: t.name,
|
|
162
|
+
hint: t.description.length > 60 ? t.description.slice(0, 57) + '...' : t.description,
|
|
163
|
+
}));
|
|
164
|
+
|
|
165
|
+
const selected = await p.select({
|
|
166
|
+
message: 'Choose a template',
|
|
167
|
+
options,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
if (p.isCancel(selected)) {
|
|
171
|
+
p.cancel('Operation cancelled.');
|
|
172
|
+
process.exit(0);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return selected;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function runNew(opts) {
|
|
179
|
+
const { name, git, yes } = opts;
|
|
180
|
+
|
|
181
|
+
if (!name || name === '.' || name === '..') {
|
|
182
|
+
logError('❌ Please provide a valid project name (e.g. "my-api").');
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const allTemplates = getTemplatesWithManifests();
|
|
187
|
+
if (allTemplates.length === 0) {
|
|
188
|
+
logError('❌ No templates found in templates/ directory.');
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
let chosenTemplateId = opts.template;
|
|
193
|
+
let finalOwner = opts.owner || 'jml6m';
|
|
194
|
+
let finalDesc = opts.description || 'A new project scaffolded with skeletor.';
|
|
195
|
+
|
|
196
|
+
const isInteractive = !yes && process.stdout.isTTY;
|
|
197
|
+
|
|
198
|
+
if (!chosenTemplateId) {
|
|
199
|
+
chosenTemplateId = await chooseTemplateInteractively(allTemplates, yes);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const templateInfo = allTemplates.find((t) => t.id === chosenTemplateId);
|
|
203
|
+
if (!templateInfo) {
|
|
204
|
+
const available = allTemplates.map((t) => t.id).join(', ');
|
|
205
|
+
logError(`❌ Unknown template "${chosenTemplateId}". Available: ${available}`);
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Richer interactive prompts for missing details (only when not --yes)
|
|
210
|
+
if (isInteractive) {
|
|
211
|
+
const ownerInput = await p.text({
|
|
212
|
+
message: 'GitHub owner / org',
|
|
213
|
+
placeholder: finalOwner,
|
|
214
|
+
initialValue: finalOwner,
|
|
215
|
+
});
|
|
216
|
+
if (p.isCancel(ownerInput)) { p.cancel('Cancelled.'); process.exit(0); }
|
|
217
|
+
finalOwner = ownerInput || finalOwner;
|
|
218
|
+
|
|
219
|
+
const descInput = await p.text({
|
|
220
|
+
message: 'Project description',
|
|
221
|
+
placeholder: finalDesc,
|
|
222
|
+
initialValue: finalDesc,
|
|
223
|
+
});
|
|
224
|
+
if (p.isCancel(descInput)) { p.cancel('Cancelled.'); process.exit(0); }
|
|
225
|
+
finalDesc = descInput || finalDesc;
|
|
226
|
+
|
|
227
|
+
const shouldContinue = await p.confirm({
|
|
228
|
+
message: `Scaffold "${name}" as ${templateInfo.name}?`,
|
|
229
|
+
initialValue: true,
|
|
230
|
+
});
|
|
231
|
+
if (p.isCancel(shouldContinue) || !shouldContinue) {
|
|
232
|
+
p.cancel('Scaffolding cancelled.');
|
|
233
|
+
process.exit(0);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const targetDir = path.resolve(process.cwd(), name);
|
|
238
|
+
if (fs.existsSync(targetDir) && fs.readdirSync(targetDir).length > 0) {
|
|
239
|
+
logError(`❌ Target directory "${name}" already exists and is not empty.`);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const repoName = name;
|
|
244
|
+
const vars = {
|
|
245
|
+
PROJECT_NAME: name,
|
|
246
|
+
REPO_OWNER: finalOwner,
|
|
247
|
+
REPO_NAME: repoName,
|
|
248
|
+
DESCRIPTION: finalDesc,
|
|
249
|
+
YEAR: new Date().getFullYear(),
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
p.log.info(`Creating "${name}" using ${templateInfo.name}...`);
|
|
253
|
+
|
|
254
|
+
copyAndRender(templateInfo.dir, targetDir, vars);
|
|
255
|
+
|
|
256
|
+
if (git) {
|
|
257
|
+
try {
|
|
258
|
+
execSync('git init -q', { cwd: targetDir, stdio: 'ignore' });
|
|
259
|
+
execSync('git add -A', { cwd: targetDir, stdio: 'ignore' });
|
|
260
|
+
execSync('git commit -q -m "chore: initial commit from skeletor"', { cwd: targetDir, stdio: 'ignore' });
|
|
261
|
+
p.log.success('Git repository initialized');
|
|
262
|
+
} catch (e) {
|
|
263
|
+
p.log.warn('Git init skipped (git not available or failed)');
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
p.outro('✅ Done!');
|
|
268
|
+
|
|
269
|
+
console.log(` cd ${name}`);
|
|
270
|
+
const suggested = templateInfo.verifyCommands && templateInfo.verifyCommands.length
|
|
271
|
+
? templateInfo.verifyCommands[0]
|
|
272
|
+
: 'Run your language-specific install + test commands';
|
|
273
|
+
console.log(` ${suggested}`);
|
|
274
|
+
console.log(' Then run the other steps from the template manifest (lint, test, etc.)');
|
|
275
|
+
console.log('\n Edit AGENTS.md (or equivalent) to customize the AI contract.');
|
|
276
|
+
console.log(' Happy scaffolding. Your rules, your choice of stack.\n');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function main() {
|
|
280
|
+
const opts = parseArgs(process.argv);
|
|
281
|
+
|
|
282
|
+
if (opts.command === 'help' || !opts.command) {
|
|
283
|
+
log(USAGE.trim());
|
|
284
|
+
const tmpls = getTemplatesWithManifests();
|
|
285
|
+
if (tmpls.length) {
|
|
286
|
+
log('\nAvailable templates:');
|
|
287
|
+
tmpls.forEach((t) => log(` • ${t.id} — ${t.name}: ${t.description}`));
|
|
288
|
+
} else {
|
|
289
|
+
log('\n(No templates found yet — run from a full skeletor checkout with templates/ )');
|
|
290
|
+
}
|
|
291
|
+
process.exit(0);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (opts.command === 'new') {
|
|
295
|
+
runNew(opts).catch((e) => {
|
|
296
|
+
logError('❌ Generation failed: ' + (e?.message || e));
|
|
297
|
+
process.exit(1);
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Exports for testing / programmatic use
|
|
303
|
+
export {
|
|
304
|
+
parseArgs,
|
|
305
|
+
render,
|
|
306
|
+
getAvailableTemplates,
|
|
307
|
+
getTemplatesWithManifests,
|
|
308
|
+
loadTemplateManifest,
|
|
309
|
+
ensureDir,
|
|
310
|
+
copyAndRender,
|
|
311
|
+
runNew,
|
|
312
|
+
USAGE,
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const isTestEnv = process.env.SKELETOR_CLI_TEST === '1' || process.env.JEST_WORKER_ID != null;
|
|
316
|
+
if (!isTestEnv) {
|
|
317
|
+
main();
|
|
318
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# C# Project — Agent Guidelines
|
|
2
|
+
|
|
3
|
+
**Project:** {{PROJECT_NAME}}
|
|
4
|
+
**Language:** C# (.NET)
|
|
5
|
+
|
|
6
|
+
- Build: `dotnet build`, test: `dotnet test`.
|
|
7
|
+
- Use modern C# features (records, pattern matching, nullable).
|
|
8
|
+
- Prefer xUnit for tests.
|
|
9
|
+
- Keep Program.cs or entry points minimal.
|
|
10
|
+
- Update this file with any additional .NET conventions.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// {{PROJECT_NAME}}
|
|
2
|
+
// {{DESCRIPTION}}
|
|
3
|
+
|
|
4
|
+
namespace {{PROJECT_NAME | replace('-', '_')}};
|
|
5
|
+
|
|
6
|
+
class Program
|
|
7
|
+
{
|
|
8
|
+
static void Main(string[] args)
|
|
9
|
+
{
|
|
10
|
+
var name = args.Length > 0 ? args[0] : "world";
|
|
11
|
+
Console.WriteLine(Hello(name));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public static string Hello(string name)
|
|
15
|
+
{
|
|
16
|
+
return $"Hello, {name} from {{PROJECT_NAME}}!";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<Project Sdk="Microsoft.NET.Sdk">
|
|
2
|
+
|
|
3
|
+
<PropertyGroup>
|
|
4
|
+
<OutputType>Exe</OutputType>
|
|
5
|
+
<TargetFramework>net8.0</TargetFramework>
|
|
6
|
+
<ImplicitUsings>enable</ImplicitUsings>
|
|
7
|
+
<Nullable>enable</Nullable>
|
|
8
|
+
<RootNamespace>{{PROJECT_NAME | replace('-', '_')}}</RootNamespace>
|
|
9
|
+
</PropertyGroup>
|
|
10
|
+
|
|
11
|
+
<ItemGroup>
|
|
12
|
+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
|
13
|
+
<PackageReference Include="xunit" Version="2.6.6" />
|
|
14
|
+
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6" />
|
|
15
|
+
</ItemGroup>
|
|
16
|
+
|
|
17
|
+
</Project>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Go Project — Agent Guidelines
|
|
2
|
+
|
|
3
|
+
**Project:** {{PROJECT_NAME}}
|
|
4
|
+
**Language:** Go
|
|
5
|
+
|
|
6
|
+
- Use `go build`, `go test`, `go mod tidy`.
|
|
7
|
+
- Keep packages small and well-tested.
|
|
8
|
+
- Follow standard Go conventions (gofmt, effective go).
|
|
9
|
+
- Update this file + README when standards evolve.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"os"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
// main is the entry point for {{PROJECT_NAME}}.
|
|
9
|
+
func main() {
|
|
10
|
+
name := "world"
|
|
11
|
+
if len(os.Args) > 1 {
|
|
12
|
+
name = os.Args[1]
|
|
13
|
+
}
|
|
14
|
+
fmt.Println(hello(name))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// hello returns a greeting. Extracted for easy testing.
|
|
18
|
+
func hello(name string) string {
|
|
19
|
+
return fmt.Sprintf("Hello, %s from {{PROJECT_NAME}}!", name)
|
|
20
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "go",
|
|
3
|
+
"name": "Go",
|
|
4
|
+
"description": "Standard Go module with go.mod, main package, basic CLI structure, and tests. Uses go test for verification.",
|
|
5
|
+
"language": "go",
|
|
6
|
+
"verifyCommands": [
|
|
7
|
+
"go mod tidy",
|
|
8
|
+
"go build .",
|
|
9
|
+
"go test ./..."
|
|
10
|
+
]
|
|
11
|
+
}
|