@morda-dev/create-sdk 1.1.1 → 1.1.4
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 +95 -23
- package/bin/create-sdk.js +19 -185
- package/package.json +21 -4
- package/src/cli/args.js +14 -0
- package/src/cli/prompts.js +2 -0
- package/src/cli/run.js +80 -0
- package/src/cli/scaffold.js +107 -0
- package/src/cli/utils.js +28 -0
- package/template/package.json +0 -15
package/README.md
CHANGED
|
@@ -1,9 +1,31 @@
|
|
|
1
1
|
# create-sdk
|
|
2
|
+

|
|
3
|
+

|
|
4
|
+

|
|
2
5
|
|
|
3
|
-
Scaffold a production-ready TypeScript SDK with a strict public API
|
|
6
|
+
> Scaffold a **production-ready TypeScript SDK** with a **strict public API**.
|
|
7
|
+
|
|
8
|
+
`create-sdk` is a CLI tool that bootstraps a modern TypeScript SDK with
|
|
9
|
+
**ESM-first setup**, **API Extractor**, and **CI-ready structure** — so you
|
|
10
|
+
can focus on features, not boilerplate.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## ✨ Features
|
|
15
|
+
|
|
16
|
+
- ⚡ **Interactive CLI** (or fully automated with flags)
|
|
17
|
+
- 📦 **ESM-first TypeScript setup**
|
|
18
|
+
- 🔒 **Strict public API** via API Extractor
|
|
19
|
+
- 🧪 **CI-verified & smoke-tested**
|
|
20
|
+
- 🧱 Clean, scalable project structure
|
|
21
|
+
- 🚫 No hidden magic, no vendor lock-in
|
|
22
|
+
|
|
23
|
+
---
|
|
4
24
|
|
|
5
25
|
## 🚀 Quick start
|
|
6
26
|
|
|
27
|
+
This creates a clean SDK with a strict public API enforced by API Extractor.
|
|
28
|
+
|
|
7
29
|
```bash
|
|
8
30
|
npx @morda-dev/create-sdk my-sdk
|
|
9
31
|
cd my-sdk
|
|
@@ -11,41 +33,91 @@ npm install
|
|
|
11
33
|
npm run build
|
|
12
34
|
```
|
|
13
35
|
|
|
14
|
-
|
|
36
|
+
That’s it. You now have a production-ready SDK.
|
|
37
|
+
|
|
38
|
+
## 📘 Example SDK
|
|
39
|
+
|
|
40
|
+
See a minimal, production-style SDK generated with **create-sdk**:
|
|
41
|
+
|
|
42
|
+
👉 https://github.com/mordaHQ/example-sdk
|
|
43
|
+
|
|
44
|
+
This example demonstrates:
|
|
45
|
+
- a single, explicit public API entrypoint (`src/index.ts`)
|
|
46
|
+
- API Extractor–controlled surface
|
|
47
|
+
- ESM-first configuration
|
|
48
|
+
- CI-ready setup
|
|
49
|
+
- safe evolution without accidental breaking changes
|
|
50
|
+
|
|
51
|
+
Use it as a reference for structuring real-world SDKs.
|
|
52
|
+
|
|
53
|
+
## ❓ Why create-sdk?
|
|
54
|
+
|
|
55
|
+
Most SDK starters fall into one of two traps:
|
|
56
|
+
|
|
57
|
+
- ❌ too minimal — no real production setup
|
|
58
|
+
- ❌ too complex — opinionated, hard to extend
|
|
15
59
|
|
|
16
|
-
|
|
60
|
+
**create-sdk** sits in the middle:
|
|
17
61
|
|
|
18
|
-
|
|
62
|
+
> Strict where it matters. Flexible where it should be.
|
|
19
63
|
|
|
20
|
-
|
|
64
|
+
You get:
|
|
21
65
|
|
|
22
|
-
|
|
66
|
+
- a clearly defined public API
|
|
67
|
+
- predictable build & release flow
|
|
68
|
+
- freedom to grow the SDK your way
|
|
23
69
|
|
|
24
|
-
|
|
70
|
+
## 🧩 What’s included
|
|
25
71
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
72
|
+
- TypeScript project with strict config
|
|
73
|
+
- API Extractor setup
|
|
74
|
+
- Ready-to-use CI workflow
|
|
75
|
+
- Clean module-based structure
|
|
76
|
+
- Example public API and types
|
|
31
77
|
|
|
32
|
-
|
|
78
|
+
## 🛠 CLI options
|
|
33
79
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
80
|
+
```bash
|
|
81
|
+
npx @morda-dev/create-sdk <name> [options]
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Options
|
|
85
|
+
|
|
86
|
+
- `--yes` — skip all prompts
|
|
87
|
+
- `--no-install` — skip dependency installation
|
|
88
|
+
- `--no-git` — skip git initialization
|
|
89
|
+
|
|
90
|
+
### Example
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npx @morda-dev/create-sdk my-sdk --yes --no-install
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## 📦 Requirements
|
|
97
|
+
|
|
98
|
+
- Node.js >= 18
|
|
99
|
+
- npm / pnpm / yarn
|
|
100
|
+
|
|
101
|
+
## ✅ Used in production
|
|
37
102
|
|
|
38
|
-
|
|
103
|
+
This template is used to scaffold and maintain real-world TypeScript SDKs.
|
|
39
104
|
|
|
40
|
-
This CLI helps you build SDKs with a stable and explicit public API.
|
|
41
105
|
|
|
42
|
-
|
|
43
|
-
This prevents accidental breaking changes and enforces long-term stability.
|
|
106
|
+
## 🧭 Roadmap
|
|
44
107
|
|
|
45
|
-
|
|
108
|
+
- Custom templates
|
|
109
|
+
- Optional monorepo mode
|
|
110
|
+
- Plugin system
|
|
111
|
+
- SDK release automation helpers
|
|
46
112
|
|
|
47
|
-
|
|
113
|
+
## 🤝 Contributing
|
|
114
|
+
|
|
115
|
+
Issues and PRs are welcome.
|
|
116
|
+
This project is intentionally kept small, clean, and well-documented.
|
|
48
117
|
|
|
49
118
|
## 📄 License
|
|
50
119
|
|
|
51
|
-
ISC
|
|
120
|
+
ISC
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
---
|
package/bin/create-sdk.js
CHANGED
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import fs from "fs";
|
|
4
|
-
import path from "path";
|
|
5
2
|
import process from "process";
|
|
6
|
-
import { fileURLToPath } from "url";
|
|
7
|
-
import { execSync } from "child_process";
|
|
8
3
|
import prompts from "prompts";
|
|
4
|
+
import { scaffold } from "../src/cli/scaffold.js";
|
|
9
5
|
|
|
10
6
|
/* ============================================================================
|
|
11
|
-
*
|
|
7
|
+
* Node version check
|
|
12
8
|
* ============================================================================
|
|
13
9
|
*/
|
|
14
|
-
|
|
15
10
|
const REQUIRED_NODE_MAJOR = 18;
|
|
16
11
|
const CURRENT_NODE_MAJOR = Number(process.versions.node.split(".")[0]);
|
|
17
12
|
|
|
@@ -24,67 +19,30 @@ if (CURRENT_NODE_MAJOR < REQUIRED_NODE_MAJOR) {
|
|
|
24
19
|
}
|
|
25
20
|
|
|
26
21
|
/* ============================================================================
|
|
27
|
-
*
|
|
22
|
+
* Args parsing
|
|
28
23
|
* ============================================================================
|
|
29
24
|
*/
|
|
30
|
-
|
|
31
25
|
const rawArgs = process.argv.slice(2);
|
|
32
26
|
|
|
33
27
|
const flags = {
|
|
34
28
|
yes: rawArgs.includes("--yes"),
|
|
35
29
|
noGit: rawArgs.includes("--no-git"),
|
|
36
30
|
noInstall: rawArgs.includes("--no-install"),
|
|
31
|
+
dryRun: rawArgs.includes("--dry-run"),
|
|
32
|
+
force: rawArgs.includes("--force"),
|
|
37
33
|
};
|
|
38
34
|
|
|
39
|
-
|
|
40
|
-
console.log("create-sdk v1.1.0");
|
|
41
|
-
process.exit(0);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
|
|
45
|
-
console.log(`
|
|
46
|
-
create-sdk — scaffold a production-ready TypeScript SDK
|
|
47
|
-
|
|
48
|
-
Usage:
|
|
49
|
-
npx @morda-dev/create-sdk <project-name>
|
|
50
|
-
|
|
51
|
-
Options:
|
|
52
|
-
--yes Skip prompts and use defaults
|
|
53
|
-
--no-git Do not initialize git repository
|
|
54
|
-
--no-install Do not install dependencies
|
|
55
|
-
--help, -h Show this help
|
|
56
|
-
--version, -v Show version
|
|
57
|
-
|
|
58
|
-
Example:
|
|
59
|
-
npx @morda-dev/create-sdk my-sdk
|
|
60
|
-
npx @morda-dev/create-sdk my-sdk --yes --no-install
|
|
61
|
-
`);
|
|
62
|
-
process.exit(0);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/* ============================================================================
|
|
66
|
-
* Paths
|
|
67
|
-
* ============================================================================
|
|
68
|
-
*/
|
|
69
|
-
|
|
70
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
71
|
-
const __dirname = path.dirname(__filename);
|
|
72
|
-
const templateDir = path.resolve(__dirname, "../template");
|
|
35
|
+
const projectName = rawArgs.find((a) => !a.startsWith("-"));
|
|
73
36
|
|
|
74
37
|
/* ============================================================================
|
|
75
|
-
*
|
|
38
|
+
* Interactive mode
|
|
76
39
|
* ============================================================================
|
|
77
40
|
*/
|
|
78
|
-
|
|
79
|
-
const argProjectName = rawArgs.find((a) => !a.startsWith("-"));
|
|
80
|
-
|
|
81
|
-
let projectName;
|
|
41
|
+
let name = projectName;
|
|
82
42
|
let initGit = !flags.noGit;
|
|
83
43
|
let installDeps = !flags.noInstall;
|
|
84
44
|
|
|
85
|
-
if (flags.yes &&
|
|
86
|
-
projectName = argProjectName;
|
|
87
|
-
} else if (!argProjectName) {
|
|
45
|
+
if (!flags.yes && !name) {
|
|
88
46
|
const answers = await prompts(
|
|
89
47
|
[
|
|
90
48
|
{
|
|
@@ -114,148 +72,24 @@ if (flags.yes && argProjectName) {
|
|
|
114
72
|
}
|
|
115
73
|
);
|
|
116
74
|
|
|
117
|
-
|
|
75
|
+
name = answers.name;
|
|
118
76
|
initGit = answers.initGit;
|
|
119
77
|
installDeps = answers.installDeps;
|
|
120
|
-
} else {
|
|
121
|
-
projectName = argProjectName;
|
|
122
78
|
}
|
|
123
79
|
|
|
124
|
-
if (!
|
|
80
|
+
if (!name) {
|
|
125
81
|
console.error("❌ Project name is required");
|
|
126
82
|
process.exit(1);
|
|
127
83
|
}
|
|
128
84
|
|
|
129
85
|
/* ============================================================================
|
|
130
|
-
*
|
|
131
|
-
* ============================================================================
|
|
132
|
-
*/
|
|
133
|
-
|
|
134
|
-
const dirName = projectName.startsWith("@")
|
|
135
|
-
? projectName.split("/")[1]
|
|
136
|
-
: projectName;
|
|
137
|
-
|
|
138
|
-
const targetDir = path.resolve(process.cwd(), dirName);
|
|
139
|
-
|
|
140
|
-
/* ============================================================================
|
|
141
|
-
* Safety checks
|
|
142
|
-
* ============================================================================
|
|
143
|
-
*/
|
|
144
|
-
|
|
145
|
-
if (!fs.existsSync(templateDir)) {
|
|
146
|
-
console.error("❌ Template directory not found:", templateDir);
|
|
147
|
-
process.exit(1);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (fs.existsSync(targetDir)) {
|
|
151
|
-
console.error(`❌ Directory "${dirName}" already exists`);
|
|
152
|
-
process.exit(1);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/* ============================================================================
|
|
156
|
-
* Scaffold project
|
|
157
|
-
* ============================================================================
|
|
158
|
-
*/
|
|
159
|
-
|
|
160
|
-
console.log("🚀 Creating SDK:", projectName);
|
|
161
|
-
|
|
162
|
-
copyDir(templateDir, targetDir);
|
|
163
|
-
|
|
164
|
-
/* ============================================================================
|
|
165
|
-
* Update package.json
|
|
86
|
+
* Execute scaffold
|
|
166
87
|
* ============================================================================
|
|
167
88
|
*/
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
177
|
-
|
|
178
|
-
/* ============================================================================
|
|
179
|
-
* Update README title
|
|
180
|
-
* ============================================================================
|
|
181
|
-
*/
|
|
182
|
-
|
|
183
|
-
const readmePath = path.join(targetDir, "README.md");
|
|
184
|
-
|
|
185
|
-
if (fs.existsSync(readmePath)) {
|
|
186
|
-
const updated = fs
|
|
187
|
-
.readFileSync(readmePath, "utf8")
|
|
188
|
-
.replace(/^# .*/m, `# ${projectName}`);
|
|
189
|
-
|
|
190
|
-
fs.writeFileSync(readmePath, updated);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/* ============================================================================
|
|
194
|
-
* Git init
|
|
195
|
-
* ============================================================================
|
|
196
|
-
*/
|
|
197
|
-
|
|
198
|
-
if (initGit) {
|
|
199
|
-
try {
|
|
200
|
-
execSync("git init", { cwd: targetDir, stdio: "ignore" });
|
|
201
|
-
} catch {}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/* ============================================================================
|
|
205
|
-
* Install dependencies
|
|
206
|
-
* ============================================================================
|
|
207
|
-
*/
|
|
208
|
-
|
|
209
|
-
if (installDeps) {
|
|
210
|
-
try {
|
|
211
|
-
execSync("npm install", { cwd: targetDir, stdio: "inherit" });
|
|
212
|
-
} catch {
|
|
213
|
-
console.warn("⚠️ Failed to install dependencies");
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/* ============================================================================
|
|
218
|
-
* Done
|
|
219
|
-
* ============================================================================
|
|
220
|
-
*/
|
|
221
|
-
|
|
222
|
-
console.log("");
|
|
223
|
-
console.log("🎉 SDK scaffolded successfully!");
|
|
224
|
-
console.log("");
|
|
225
|
-
console.log("Next steps:");
|
|
226
|
-
console.log(` cd ${dirName}`);
|
|
227
|
-
if (!installDeps) console.log(" npm install");
|
|
228
|
-
console.log(" npm run build");
|
|
229
|
-
console.log("");
|
|
230
|
-
console.log("Happy hacking 🚀");
|
|
231
|
-
|
|
232
|
-
/* ============================================================================
|
|
233
|
-
* Helpers
|
|
234
|
-
* ============================================================================
|
|
235
|
-
*/
|
|
236
|
-
|
|
237
|
-
function copyDir(src, dest) {
|
|
238
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
239
|
-
|
|
240
|
-
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
241
|
-
if (shouldIgnore(entry.name)) continue;
|
|
242
|
-
|
|
243
|
-
const srcPath = path.join(src, entry.name);
|
|
244
|
-
const destPath = path.join(dest, entry.name);
|
|
245
|
-
|
|
246
|
-
if (entry.isDirectory()) {
|
|
247
|
-
copyDir(srcPath, destPath);
|
|
248
|
-
} else {
|
|
249
|
-
fs.copyFileSync(srcPath, destPath);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
function shouldIgnore(name) {
|
|
255
|
-
return (
|
|
256
|
-
name === "node_modules" ||
|
|
257
|
-
name === ".git" ||
|
|
258
|
-
name === "dist" ||
|
|
259
|
-
name === ".DS_Store"
|
|
260
|
-
);
|
|
261
|
-
}
|
|
89
|
+
await scaffold({
|
|
90
|
+
projectName: name,
|
|
91
|
+
initGit,
|
|
92
|
+
installDeps,
|
|
93
|
+
dryRun: flags.dryRun,
|
|
94
|
+
force: flags.force,
|
|
95
|
+
});
|
package/package.json
CHANGED
|
@@ -1,27 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morda-dev/create-sdk",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "CLI to scaffold a production-ready TypeScript SDK with a strict public API",
|
|
5
|
+
"license": "ISC",
|
|
5
6
|
"type": "module",
|
|
7
|
+
|
|
6
8
|
"bin": {
|
|
7
9
|
"create-sdk": "./bin/create-sdk.js"
|
|
8
10
|
},
|
|
11
|
+
|
|
9
12
|
"files": [
|
|
10
13
|
"bin",
|
|
14
|
+
"src",
|
|
11
15
|
"template",
|
|
12
|
-
"README.md"
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
13
18
|
],
|
|
19
|
+
|
|
14
20
|
"engines": {
|
|
15
21
|
"node": ">=18"
|
|
16
22
|
},
|
|
23
|
+
|
|
17
24
|
"repository": {
|
|
18
25
|
"type": "git",
|
|
19
26
|
"url": "https://github.com/mordaHQ/create-sdk.git"
|
|
20
27
|
},
|
|
28
|
+
|
|
21
29
|
"homepage": "https://github.com/mordaHQ/create-sdk",
|
|
22
30
|
"bugs": {
|
|
23
31
|
"url": "https://github.com/mordaHQ/create-sdk/issues"
|
|
24
32
|
},
|
|
33
|
+
|
|
25
34
|
"keywords": [
|
|
26
35
|
"create-sdk",
|
|
27
36
|
"typescript",
|
|
@@ -29,13 +38,21 @@
|
|
|
29
38
|
"cli",
|
|
30
39
|
"scaffold",
|
|
31
40
|
"api-extractor",
|
|
32
|
-
"library"
|
|
41
|
+
"library",
|
|
42
|
+
"esm",
|
|
43
|
+
"developer-tools"
|
|
33
44
|
],
|
|
45
|
+
|
|
34
46
|
"scripts": {
|
|
35
47
|
"test:smoke": "node ./bin/create-sdk.js __tmp-smoke-sdk --yes --no-install --no-git && node -e \"import('fs').then(fs => fs.rmSync('__tmp-smoke-sdk', { recursive: true, force: true }))\""
|
|
36
48
|
},
|
|
49
|
+
|
|
37
50
|
"dependencies": {
|
|
38
51
|
"prompts": "^2.4.2"
|
|
39
52
|
},
|
|
40
|
-
|
|
53
|
+
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/node": "^18.19.0",
|
|
56
|
+
"undici-types": "^7.16.0"
|
|
57
|
+
}
|
|
41
58
|
}
|
package/src/cli/args.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function parseArgs(rawArgs) {
|
|
2
|
+
const flags = {
|
|
3
|
+
yes: rawArgs.includes("--yes"),
|
|
4
|
+
noGit: rawArgs.includes("--no-git"),
|
|
5
|
+
noInstall: rawArgs.includes("--no-install"),
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const projectName = rawArgs.find((a) => !a.startsWith("-"));
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
projectName,
|
|
12
|
+
flags,
|
|
13
|
+
};
|
|
14
|
+
}
|
package/src/cli/run.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import prompts from "prompts";
|
|
2
|
+
import { parseArgs } from "./args.js";
|
|
3
|
+
import { scaffold } from "./scaffold.js";
|
|
4
|
+
|
|
5
|
+
export async function run(rawArgs) {
|
|
6
|
+
const { projectName, flags } = parseArgs(rawArgs);
|
|
7
|
+
|
|
8
|
+
const dryRun = flags.dryRun === true;
|
|
9
|
+
|
|
10
|
+
// ─────────────────────────────────────────────
|
|
11
|
+
// DRY-RUN MODE — NO INTERACTION, NO SIDE EFFECTS
|
|
12
|
+
// ─────────────────────────────────────────────
|
|
13
|
+
if (dryRun && !projectName) {
|
|
14
|
+
throw new Error("Project name is required when using --dry-run");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let name = projectName;
|
|
18
|
+
let initGit = !flags.noGit;
|
|
19
|
+
let installDeps = !flags.noInstall;
|
|
20
|
+
const force = flags.force === true;
|
|
21
|
+
|
|
22
|
+
// ❗❗❗ DRY-RUN MUST OVERRIDE EVERYTHING
|
|
23
|
+
if (dryRun) {
|
|
24
|
+
initGit = false;
|
|
25
|
+
installDeps = false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ─────────────────────────────────────────────
|
|
29
|
+
// Interactive mode (ONLY if not dry-run)
|
|
30
|
+
// ─────────────────────────────────────────────
|
|
31
|
+
if (!dryRun && !flags.yes && !projectName) {
|
|
32
|
+
const answers = await prompts(
|
|
33
|
+
[
|
|
34
|
+
{
|
|
35
|
+
type: "text",
|
|
36
|
+
name: "name",
|
|
37
|
+
message: "Project name:",
|
|
38
|
+
validate: (v) => (v ? true : "Project name is required"),
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
type: "confirm",
|
|
42
|
+
name: "initGit",
|
|
43
|
+
message: "Initialize git repository?",
|
|
44
|
+
initial: true,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
type: "confirm",
|
|
48
|
+
name: "installDeps",
|
|
49
|
+
message: "Install dependencies now?",
|
|
50
|
+
initial: false,
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
{
|
|
54
|
+
onCancel: () => {
|
|
55
|
+
console.log("\n❌ Cancelled");
|
|
56
|
+
process.exit(1);
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
name = answers.name;
|
|
62
|
+
initGit = answers.initGit;
|
|
63
|
+
installDeps = answers.installDeps;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!name) {
|
|
67
|
+
throw new Error("Project name is required");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ─────────────────────────────────────────────
|
|
71
|
+
// Scaffold (SINGLE ENTRY POINT)
|
|
72
|
+
// ─────────────────────────────────────────────
|
|
73
|
+
await scaffold({
|
|
74
|
+
projectName: name,
|
|
75
|
+
initGit,
|
|
76
|
+
installDeps,
|
|
77
|
+
dryRun,
|
|
78
|
+
force,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { execSync } from "child_process";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import { copyDir } from "./utils.js";
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
const templateDir = path.resolve(__dirname, "../../template");
|
|
10
|
+
|
|
11
|
+
export async function scaffold({
|
|
12
|
+
projectName,
|
|
13
|
+
initGit = true,
|
|
14
|
+
installDeps = false,
|
|
15
|
+
dryRun = false,
|
|
16
|
+
force = false,
|
|
17
|
+
}) {
|
|
18
|
+
if (!projectName) {
|
|
19
|
+
throw new Error("Project name is required");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const dirName = projectName.startsWith("@")
|
|
23
|
+
? projectName.split("/")[1]
|
|
24
|
+
: projectName;
|
|
25
|
+
|
|
26
|
+
const targetDir = path.resolve(process.cwd(), dirName);
|
|
27
|
+
|
|
28
|
+
// ─────────────────────────────────────────────
|
|
29
|
+
// 🧪 DRY-RUN — ABSOLUTE NO-SIDE-EFFECT MODE
|
|
30
|
+
// ─────────────────────────────────────────────
|
|
31
|
+
if (dryRun) {
|
|
32
|
+
console.log("🧪 [dry-run] Creating SDK:", projectName);
|
|
33
|
+
console.log("🧪 [dry-run] Target directory:", path.join("<cwd>", dirName));
|
|
34
|
+
console.log("🧪 [dry-run] Would copy template from:", templateDir);
|
|
35
|
+
if (initGit) console.log("🧪 [dry-run] Would init git");
|
|
36
|
+
if (installDeps) console.log("🧪 [dry-run] Would install dependencies");
|
|
37
|
+
console.log("🧪 [dry-run] Done (NO filesystem changes)");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ─────────────────────────────────────────────
|
|
42
|
+
// REAL MODE — FILESYSTEM IS TOUCHED ONLY HERE
|
|
43
|
+
// ─────────────────────────────────────────────
|
|
44
|
+
if (!fs.existsSync(templateDir)) {
|
|
45
|
+
throw new Error(`Template directory not found: ${templateDir}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (fs.existsSync(targetDir)) {
|
|
49
|
+
if (!force) {
|
|
50
|
+
throw new Error(`Directory "${dirName}" already exists`);
|
|
51
|
+
}
|
|
52
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log("🚀 Creating SDK:", projectName);
|
|
56
|
+
|
|
57
|
+
// copy template
|
|
58
|
+
copyDir(templateDir, targetDir);
|
|
59
|
+
|
|
60
|
+
// package.json
|
|
61
|
+
const pkgPath = path.join(targetDir, "package.json");
|
|
62
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
63
|
+
|
|
64
|
+
pkg.name = projectName;
|
|
65
|
+
pkg.version = "0.1.0";
|
|
66
|
+
|
|
67
|
+
// шаблон НЕ должен быть private
|
|
68
|
+
delete pkg.private;
|
|
69
|
+
|
|
70
|
+
// exports добавляются ТОЛЬКО тут
|
|
71
|
+
pkg.main = "./dist/index.js";
|
|
72
|
+
pkg.types = "./dist/index.d.ts";
|
|
73
|
+
pkg.exports = {
|
|
74
|
+
".": {
|
|
75
|
+
import: "./dist/index.js",
|
|
76
|
+
types: "./dist/index.d.ts",
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
81
|
+
|
|
82
|
+
// README
|
|
83
|
+
const readmePath = path.join(targetDir, "README.md");
|
|
84
|
+
if (fs.existsSync(readmePath)) {
|
|
85
|
+
const updated = fs
|
|
86
|
+
.readFileSync(readmePath, "utf8")
|
|
87
|
+
.replace(/^# .*/m, `# ${projectName}`);
|
|
88
|
+
fs.writeFileSync(readmePath, updated);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// git
|
|
92
|
+
if (initGit) {
|
|
93
|
+
try {
|
|
94
|
+
execSync("git init", { cwd: targetDir, stdio: "ignore" });
|
|
95
|
+
} catch {}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// deps
|
|
99
|
+
if (installDeps) {
|
|
100
|
+
execSync("npm install", {
|
|
101
|
+
cwd: targetDir,
|
|
102
|
+
stdio: "inherit",
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
console.log("🎉 SDK scaffolded successfully!");
|
|
107
|
+
}
|
package/src/cli/utils.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
export function copyDir(src, dest) {
|
|
5
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
6
|
+
|
|
7
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
8
|
+
if (shouldIgnore(entry.name)) continue;
|
|
9
|
+
|
|
10
|
+
const srcPath = path.join(src, entry.name);
|
|
11
|
+
const destPath = path.join(dest, entry.name);
|
|
12
|
+
|
|
13
|
+
if (entry.isDirectory()) {
|
|
14
|
+
copyDir(srcPath, destPath);
|
|
15
|
+
} else {
|
|
16
|
+
fs.copyFileSync(srcPath, destPath);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function shouldIgnore(name) {
|
|
22
|
+
return (
|
|
23
|
+
name === "node_modules" ||
|
|
24
|
+
name === ".git" ||
|
|
25
|
+
name === "dist" ||
|
|
26
|
+
name === ".DS_Store"
|
|
27
|
+
);
|
|
28
|
+
}
|
package/template/package.json
CHANGED
|
@@ -5,21 +5,6 @@
|
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"type": "module",
|
|
7
7
|
|
|
8
|
-
"main": "./dist/index.js",
|
|
9
|
-
"types": "./dist/index.d.ts",
|
|
10
|
-
|
|
11
|
-
"exports": {
|
|
12
|
-
".": {
|
|
13
|
-
"import": "./dist/index.js",
|
|
14
|
-
"types": "./dist/index.d.ts"
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
|
-
|
|
18
|
-
"files": [
|
|
19
|
-
"dist",
|
|
20
|
-
"etc"
|
|
21
|
-
],
|
|
22
|
-
|
|
23
8
|
"sideEffects": false,
|
|
24
9
|
|
|
25
10
|
"engines": {
|