@jml6m/skeletor 0.1.0-alpha.1 → 0.1.0-alpha.5
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 +139 -7
- package/package.json +35 -4
- package/src/index.js +345 -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/Project.csproj.tmpl +17 -0
- package/templates/csharp/README.md +12 -0
- package/templates/csharp/template.json +10 -0
- package/templates/go/AGENTS.md +9 -0
- package/templates/go/README.md +13 -0
- package/templates/go/go.mod.tmpl +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.tmpl +38 -0
- package/templates/java/src/main/java/app/App.java +16 -0
- package/templates/java/src/test/java/app/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/.jscpd.json +13 -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.tmpl +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.tmpl +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 +13 -0
- package/templates/python/tests/test_main.py +9 -0
- package/templates/rust/AGENTS.md +9 -0
- package/templates/rust/Cargo.toml.tmpl +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/.jscpd.json +13 -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.tmpl +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,143 @@
|
|
|
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 (interactive mode only)
|
|
29
|
+
- Confirmation step
|
|
30
|
+
- Proper cancel handling and nice spinners/intro/outro
|
|
31
|
+
|
|
32
|
+
`--auto` stays fully non-interactive for scripts (requires `--template`; uses default owner/description).
|
|
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 -u origin HEAD`
|
|
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 explicit git 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
|
|
51
|
+
|
|
52
|
+
# Non-interactive (scripts)
|
|
53
|
+
npx @jml6m/skeletor new my-service --auto --template go
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Common flags:
|
|
57
|
+
- `--template <id>` (javascript, typescript, python, go, rust, java, csharp, ...)
|
|
58
|
+
- `--auto` (skip all prompts; requires `--template`)
|
|
59
|
+
- `--no-git` (skip git init; git is on by default)
|
|
60
|
+
|
|
61
|
+
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.
|
|
62
|
+
|
|
63
|
+
Run `skeletor` (or `node src/index.js`) with no args to see current templates.
|
|
64
|
+
|
|
65
|
+
## Philosophy / Goals
|
|
66
|
+
|
|
67
|
+
- Developers **pick** the scaffolding they want (language + flavor).
|
|
68
|
+
- Your personal conventions (from the workspaces analysis) are encoded in the relevant templates (javascript/typescript for now).
|
|
69
|
+
- Everything else starts from clean "standard main library" best practices and can be refined later.
|
|
70
|
+
- The `template.json` manifest per template is the contract for post-generation steps (this is what the test suite validates as steps 3 & 4).
|
|
71
|
+
- Easy to evolve: add/edit under `templates/<id>/`. New templates are auto-discovered by the CLI and tests.
|
|
72
|
+
|
|
73
|
+
## Status
|
|
74
|
+
|
|
75
|
+
Active. Multiple language templates + rich clack-based interactive UI. Test suite validates generation + manifest contracts for every template.
|
|
76
|
+
|
|
77
|
+
Run `skeletor` (or `node src/index.js`) with no args for current help and template list.
|
|
78
|
+
|
|
79
|
+
## For Testers & Reviewers
|
|
80
|
+
|
|
81
|
+
### Quick try (no install needed)
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# From a clone of this repo
|
|
85
|
+
node src/index.js new my-test-project --auto --template javascript
|
|
86
|
+
|
|
87
|
+
cd my-test-project
|
|
88
|
+
npm install
|
|
89
|
+
npm run format
|
|
90
|
+
npm run lint
|
|
91
|
+
npm test
|
|
92
|
+
npm run health:full
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Inspect the generated files, especially:
|
|
96
|
+
- `AGENTS.md`
|
|
97
|
+
- `release.js` (owner/repo substituted)
|
|
98
|
+
- `.prettierrc.json`, `.eslintrc.json`, `knip.json`
|
|
99
|
+
- `.husky/pre-commit` and `lint-staged` config
|
|
100
|
+
- `.github/workflows/ci.yml`
|
|
101
|
+
- `src/` structure + alias usage in `src/index.js`
|
|
102
|
+
- `package.json` scripts and `_moduleAliases`
|
|
103
|
+
|
|
104
|
+
### Running the unit + integration test suite
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
npm install # pulls jest (devDep)
|
|
108
|
+
npm test
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The tests cover:
|
|
112
|
+
- CLI argument parsing
|
|
113
|
+
- Template token rendering (`{{PROJECT_NAME}}` etc.)
|
|
114
|
+
- Full generation into temp dirs (verifies file presence, JSON validity, token substitution, new husky/github artifacts, refusal to overwrite, etc.)
|
|
115
|
+
- The generated projects are exercised for basic structure (no full `npm install` of heavy tools in every test to keep it fast).
|
|
116
|
+
|
|
117
|
+
All tests are in `tests/`.
|
|
118
|
+
|
|
119
|
+
### Updating / Adding Templates
|
|
120
|
+
|
|
121
|
+
The source of truth lives in `templates/<id>/` (each with its own `template.json`).
|
|
122
|
+
|
|
123
|
+
- Edit files inside an existing template → next generation gets the change.
|
|
124
|
+
- Add a whole new `templates/newlang/` with `template.json` + files → it appears in the picker, help, and tests automatically.
|
|
125
|
+
- **Ecosystem manifest files** (e.g. `package.json`, `pyproject.toml`, `Cargo.toml`, `pom.xml`, `*.csproj`, `go.mod`) must be named with a `.tmpl` suffix inside the template (e.g. `package.json.tmpl`). The generator strips the suffix on output, so the generated project sees the conventional filename. This prevents GitHub's dependency graph from treating template dependencies as skeletor's own.
|
|
126
|
+
|
|
127
|
+
Always run `npm test` after template work.
|
|
128
|
+
|
|
129
|
+
When happy, commit + push (see root `AGENTS.md` for the standing rule).
|
|
130
|
+
|
|
131
|
+
### GitHub push & external review
|
|
132
|
+
|
|
133
|
+
Pushing is **explicit** (not automatic). After changes:
|
|
134
|
+
- Run tests
|
|
135
|
+
- `git add -A && git commit -m "..." && git push -u origin HEAD`
|
|
136
|
+
|
|
137
|
+
See the root `AGENTS.md` (the instruction file for agents) for the exact rule.
|
|
138
|
+
|
|
139
|
+
The repo lives at https://github.com/jml6m/skeletor.
|
|
140
|
+
|
|
141
|
+
Open issues/PRs with feedback on new templates, UI, etc.
|
|
142
|
+
|
|
143
|
+
Now, finish the UI polish if needed and do the push.
|
package/package.json
CHANGED
|
@@ -1,13 +1,44 @@
|
|
|
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.5",
|
|
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
|
+
"prepublishOnly": "npm test"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/jml6m/skeletor.git"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"src",
|
|
21
|
+
"templates",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
24
|
+
"keywords": [
|
|
25
|
+
"scaffolding",
|
|
26
|
+
"generator",
|
|
27
|
+
"yeoman",
|
|
28
|
+
"template",
|
|
29
|
+
"eslint",
|
|
30
|
+
"prettier",
|
|
31
|
+
"knip"
|
|
32
|
+
],
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@clack/prompts": "^1.5.1"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"jest": "^29.7.0"
|
|
38
|
+
},
|
|
11
39
|
"author": "",
|
|
12
|
-
"license": "ISC"
|
|
40
|
+
"license": "ISC",
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=20.19.0"
|
|
43
|
+
}
|
|
13
44
|
}
|
package/src/index.js
CHANGED
|
@@ -1,7 +1,347 @@
|
|
|
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> Stack to scaffold (javascript, typescript, python, go, …)
|
|
28
|
+
Omit for an interactive language/stack picker (TTY required)
|
|
29
|
+
--auto Non-interactive; requires --template (uses defaults — edit
|
|
30
|
+
owner/description in generated files afterward)
|
|
31
|
+
--no-git Skip git init (git is initialized by default)
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
skeletor new my-api # interactive: pick stack, owner, description
|
|
35
|
+
skeletor new my-api --template typescript
|
|
36
|
+
skeletor new my-lib --auto --template go
|
|
37
|
+
skeletor new my-lib --auto --template go --no-git
|
|
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
|
+
auto: false,
|
|
51
|
+
git: true,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
|
55
|
+
result.command = 'help';
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (args[0] === 'new') {
|
|
60
|
+
result.command = 'new';
|
|
61
|
+
result.name = args[1];
|
|
62
|
+
for (let i = 2; i < args.length; i++) {
|
|
63
|
+
const a = args[i];
|
|
64
|
+
if (a === '--template' || a === '-t') result.template = args[++i] || null;
|
|
65
|
+
else if (a === '--auto') result.auto = true;
|
|
66
|
+
else if (a === '--no-git') result.git = false;
|
|
67
|
+
else if (!a.startsWith('-') && !result.name) result.name = a;
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
result.command = 'help';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function render(content, vars) {
|
|
77
|
+
let out = content;
|
|
78
|
+
for (const [k, v] of Object.entries(vars)) {
|
|
79
|
+
const token = new RegExp(`\\{\\{${k}\\}\\}`, 'g');
|
|
80
|
+
out = out.replace(token, String(v));
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Sanitize a segment for use in Java groupId / Go module paths. */
|
|
86
|
+
function sanitizeIdentifierSegment(value) {
|
|
87
|
+
return String(value).replace(/[^a-zA-Z0-9]/g, '').toLowerCase() || 'example';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Build the full token map passed to copyAndRender. */
|
|
91
|
+
function buildRenderVars({ name, owner, description }) {
|
|
92
|
+
const repoOwner = owner || 'jml6m';
|
|
93
|
+
const ownerSegment = sanitizeIdentifierSegment(repoOwner);
|
|
94
|
+
const namespace = String(name).replace(/-/g, '_');
|
|
95
|
+
return {
|
|
96
|
+
PROJECT_NAME: name,
|
|
97
|
+
REPO_OWNER: repoOwner,
|
|
98
|
+
REPO_NAME: name,
|
|
99
|
+
DESCRIPTION: description || 'A new project scaffolded with skeletor.',
|
|
100
|
+
YEAR: new Date().getFullYear(),
|
|
101
|
+
NAMESPACE: namespace,
|
|
102
|
+
GROUP_ID: `io.github.${ownerSegment}`,
|
|
103
|
+
GO_MODULE: `github.com/${repoOwner}/${name}`,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function ensureDir(dir) {
|
|
108
|
+
if (!fs.existsSync(dir)) {
|
|
109
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function copyAndRender(src, dest, vars) {
|
|
114
|
+
const stat = fs.statSync(src);
|
|
115
|
+
if (stat.isDirectory()) {
|
|
116
|
+
ensureDir(dest);
|
|
117
|
+
for (const entry of fs.readdirSync(src)) {
|
|
118
|
+
// never copy node_modules, .git, or skeletor manifest inside templates
|
|
119
|
+
if (entry === 'node_modules' || entry === '.git' || entry === 'template.json') continue;
|
|
120
|
+
// strip .tmpl suffix from output filename so e.g. package.json.tmpl → package.json
|
|
121
|
+
const outEntry = entry.endsWith('.tmpl') ? entry.slice(0, -'.tmpl'.length) : entry;
|
|
122
|
+
copyAndRender(path.join(src, entry), path.join(dest, outEntry), vars);
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// text file -> render
|
|
128
|
+
let content = fs.readFileSync(src, 'utf8');
|
|
129
|
+
content = render(content, vars);
|
|
130
|
+
ensureDir(path.dirname(dest));
|
|
131
|
+
fs.writeFileSync(dest, content, 'utf8');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function getAvailableTemplates() {
|
|
135
|
+
if (!fs.existsSync(TEMPLATES_DIR)) return [];
|
|
136
|
+
return fs.readdirSync(TEMPLATES_DIR).filter((d) => {
|
|
137
|
+
const p = path.join(TEMPLATES_DIR, d);
|
|
138
|
+
return fs.statSync(p).isDirectory();
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function loadTemplateManifest(templateId) {
|
|
143
|
+
const dir = path.join(TEMPLATES_DIR, templateId);
|
|
144
|
+
const manifestPath = path.join(dir, 'template.json');
|
|
145
|
+
let manifest = {
|
|
146
|
+
id: templateId,
|
|
147
|
+
name: templateId,
|
|
148
|
+
description: 'A custom template.',
|
|
149
|
+
language: templateId,
|
|
150
|
+
verifyCommands: [],
|
|
151
|
+
};
|
|
152
|
+
if (fs.existsSync(manifestPath)) {
|
|
153
|
+
try {
|
|
154
|
+
const raw = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
155
|
+
manifest = { ...manifest, ...raw };
|
|
156
|
+
} catch (e) {
|
|
157
|
+
// ignore bad manifest, fall back to defaults
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return { ...manifest, dir };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function getTemplatesWithManifests() {
|
|
164
|
+
const ids = getAvailableTemplates();
|
|
165
|
+
return ids.map((id) => loadTemplateManifest(id));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function chooseTemplateInteractively(templates) {
|
|
169
|
+
if (templates.length === 0) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
p.intro('💀 Skeletor — pick your scaffolding');
|
|
174
|
+
|
|
175
|
+
const options = templates.map((t) => ({
|
|
176
|
+
value: t.id,
|
|
177
|
+
label: t.name,
|
|
178
|
+
hint: t.description.length > 60 ? t.description.slice(0, 57) + '...' : t.description,
|
|
179
|
+
}));
|
|
180
|
+
|
|
181
|
+
const selected = await p.select({
|
|
182
|
+
message: 'What language or stack are you building?',
|
|
183
|
+
options,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
if (p.isCancel(selected)) {
|
|
187
|
+
p.cancel('Operation cancelled.');
|
|
188
|
+
process.exit(0);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return selected;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const DEFAULT_OWNER = 'jml6m';
|
|
195
|
+
const DEFAULT_DESCRIPTION = 'A new project scaffolded with skeletor.';
|
|
196
|
+
|
|
197
|
+
async function runNew(opts) {
|
|
198
|
+
const { name, git, auto } = opts;
|
|
199
|
+
|
|
200
|
+
if (!name || name === '.' || name === '..') {
|
|
201
|
+
logError('❌ Please provide a valid project name (e.g. "my-api").');
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const allTemplates = getTemplatesWithManifests();
|
|
206
|
+
if (allTemplates.length === 0) {
|
|
207
|
+
logError('❌ No templates found in templates/ directory.');
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
let chosenTemplateId = opts.template;
|
|
212
|
+
let finalOwner = DEFAULT_OWNER;
|
|
213
|
+
let finalDesc = DEFAULT_DESCRIPTION;
|
|
214
|
+
|
|
215
|
+
const isInteractive = !auto && process.stdout.isTTY;
|
|
216
|
+
|
|
217
|
+
if (!chosenTemplateId) {
|
|
218
|
+
if (auto) {
|
|
219
|
+
logError('❌ --auto requires --template <id>. Pick a stack interactively by omitting --auto.');
|
|
220
|
+
logError(` Available: ${allTemplates.map((t) => t.id).join(', ')}`);
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
if (!process.stdout.isTTY) {
|
|
224
|
+
logError('❌ No --template provided and this is not an interactive terminal.');
|
|
225
|
+
logError(' Use --auto --template <id> for scripts and piped environments.');
|
|
226
|
+
logError(` Available: ${allTemplates.map((t) => t.id).join(', ')}`);
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
chosenTemplateId = await chooseTemplateInteractively(allTemplates);
|
|
230
|
+
} else if (!process.stdout.isTTY && !auto) {
|
|
231
|
+
logError('❌ --template requires --auto when not running in an interactive terminal.');
|
|
232
|
+
logError(` Example: skeletor new ${name} --auto --template ${chosenTemplateId}`);
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const templateInfo = allTemplates.find((t) => t.id === chosenTemplateId);
|
|
237
|
+
if (!templateInfo) {
|
|
238
|
+
const available = allTemplates.map((t) => t.id).join(', ');
|
|
239
|
+
logError(`❌ Unknown template "${chosenTemplateId}". Available: ${available}`);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Owner, description, and confirm — interactive only (--auto uses defaults above)
|
|
244
|
+
if (isInteractive) {
|
|
245
|
+
const ownerInput = await p.text({
|
|
246
|
+
message: 'GitHub owner / org',
|
|
247
|
+
placeholder: finalOwner,
|
|
248
|
+
initialValue: finalOwner,
|
|
249
|
+
});
|
|
250
|
+
if (p.isCancel(ownerInput)) { p.cancel('Cancelled.'); process.exit(0); }
|
|
251
|
+
finalOwner = ownerInput || finalOwner;
|
|
252
|
+
|
|
253
|
+
const descInput = await p.text({
|
|
254
|
+
message: 'Project description',
|
|
255
|
+
placeholder: finalDesc,
|
|
256
|
+
initialValue: finalDesc,
|
|
257
|
+
});
|
|
258
|
+
if (p.isCancel(descInput)) { p.cancel('Cancelled.'); process.exit(0); }
|
|
259
|
+
finalDesc = descInput || finalDesc;
|
|
260
|
+
|
|
261
|
+
const shouldContinue = await p.confirm({
|
|
262
|
+
message: `Scaffold "${name}" as ${templateInfo.name}?`,
|
|
263
|
+
initialValue: true,
|
|
264
|
+
});
|
|
265
|
+
if (p.isCancel(shouldContinue) || !shouldContinue) {
|
|
266
|
+
p.cancel('Scaffolding cancelled.');
|
|
267
|
+
process.exit(0);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const targetDir = path.resolve(process.cwd(), name);
|
|
272
|
+
if (fs.existsSync(targetDir) && fs.readdirSync(targetDir).length > 0) {
|
|
273
|
+
logError(`❌ Target directory "${name}" already exists and is not empty.`);
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const vars = buildRenderVars({ name, owner: finalOwner, description: finalDesc });
|
|
278
|
+
|
|
279
|
+
p.log.info(`Creating "${name}" using ${templateInfo.name}...`);
|
|
280
|
+
|
|
281
|
+
copyAndRender(templateInfo.dir, targetDir, vars);
|
|
282
|
+
|
|
283
|
+
if (git) {
|
|
284
|
+
try {
|
|
285
|
+
execSync('git init -q', { cwd: targetDir, stdio: 'ignore' });
|
|
286
|
+
execSync('git add -A', { cwd: targetDir, stdio: 'ignore' });
|
|
287
|
+
execSync('git commit -q -m "chore: initial commit from skeletor"', { cwd: targetDir, stdio: 'ignore' });
|
|
288
|
+
p.log.success('Git repository initialized');
|
|
289
|
+
} catch (e) {
|
|
290
|
+
p.log.warn('Git init skipped (git not available or failed)');
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
p.outro('✅ Done!');
|
|
295
|
+
|
|
296
|
+
console.log(` cd ${name}`);
|
|
297
|
+
const suggested = templateInfo.verifyCommands && templateInfo.verifyCommands.length
|
|
298
|
+
? templateInfo.verifyCommands[0]
|
|
299
|
+
: 'Run your language-specific install + test commands';
|
|
300
|
+
console.log(` ${suggested}`);
|
|
301
|
+
console.log(' Then run the other steps from the template manifest (lint, test, etc.)');
|
|
302
|
+
console.log('\n Edit AGENTS.md (or equivalent) to customize the AI contract.');
|
|
303
|
+
console.log(' Happy scaffolding. Your rules, your choice of stack.\n');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function main() {
|
|
307
|
+
const opts = parseArgs(process.argv);
|
|
308
|
+
|
|
309
|
+
if (opts.command === 'help' || !opts.command) {
|
|
310
|
+
log(USAGE.trim());
|
|
311
|
+
const tmpls = getTemplatesWithManifests();
|
|
312
|
+
if (tmpls.length) {
|
|
313
|
+
log('\nAvailable templates:');
|
|
314
|
+
tmpls.forEach((t) => log(` • ${t.id} — ${t.name}: ${t.description}`));
|
|
315
|
+
} else {
|
|
316
|
+
log('\n(No templates found yet — run from a full skeletor checkout with templates/ )');
|
|
317
|
+
}
|
|
318
|
+
process.exit(0);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (opts.command === 'new') {
|
|
322
|
+
runNew(opts).catch((e) => {
|
|
323
|
+
logError('❌ Generation failed: ' + (e?.message || e));
|
|
324
|
+
process.exit(1);
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Exports for testing / programmatic use
|
|
330
|
+
export {
|
|
331
|
+
parseArgs,
|
|
332
|
+
render,
|
|
333
|
+
buildRenderVars,
|
|
334
|
+
sanitizeIdentifierSegment,
|
|
335
|
+
getAvailableTemplates,
|
|
336
|
+
getTemplatesWithManifests,
|
|
337
|
+
loadTemplateManifest,
|
|
338
|
+
ensureDir,
|
|
339
|
+
copyAndRender,
|
|
340
|
+
runNew,
|
|
341
|
+
USAGE,
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
const isTestEnv = process.env.SKELETOR_CLI_TEST === '1' || process.env.JEST_WORKER_ID != null;
|
|
345
|
+
if (!isTestEnv) {
|
|
346
|
+
main();
|
|
347
|
+
}
|
|
@@ -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 {{NAMESPACE}};
|
|
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>{{NAMESPACE}}</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
|
+
}
|