@morda-dev/create-sdk 1.1.3 → 1.2.0

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 CHANGED
@@ -1,123 +1,128 @@
1
- # create-sdk
2
- ![npm](https://img.shields.io/npm/v/@morda-dev/create-sdk)
3
- ![npm downloads](https://img.shields.io/npm/dm/@morda-dev/create-sdk)
4
- ![CI](https://github.com/mordaHQ/create-sdk/actions/workflows/ci.yml/badge.svg)
5
-
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
- ---
24
-
25
- ## 🚀 Quick start
26
-
27
- This creates a clean SDK with a strict public API enforced by API Extractor.
28
-
29
- ```bash
30
- npx @morda-dev/create-sdk my-sdk
31
- cd my-sdk
32
- npm install
33
- npm run build
34
- ```
35
-
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
59
-
60
- **create-sdk** sits in the middle:
61
-
62
- > Strict where it matters. Flexible where it should be.
63
-
64
- You get:
65
-
66
- - a clearly defined public API
67
- - predictable build & release flow
68
- - freedom to grow the SDK your way
69
-
70
- ## 🧩 What’s included
71
-
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
77
-
78
- ## 🛠 CLI options
79
-
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
102
-
103
- This template is used to scaffold and maintain real-world TypeScript SDKs.
104
-
105
-
106
- ## 🧭 Roadmap
107
-
108
- - Custom templates
109
- - Optional monorepo mode
110
- - Plugin system
111
- - SDK release automation helpers
112
-
113
- ## 🤝 Contributing
114
-
115
- Issues and PRs are welcome.
116
- This project is intentionally kept small, clean, and well-documented.
117
-
118
- ## 📄 License
119
-
120
- ISC
121
-
122
-
123
- ---
1
+ # create-sdk
2
+ ![npm](https://img.shields.io/npm/v/@morda-dev/create-sdk)
3
+ ![npm downloads](https://img.shields.io/npm/dm/@morda-dev/create-sdk)
4
+ ![CI](https://github.com/mordaHQ/create-sdk/actions/workflows/ci.yml/badge.svg)
5
+
6
+ > Scaffold a **production-ready TypeScript SDK** with a **strict public API**.
7
+
8
+ > ⚠️ Early-stage project (v0.x).
9
+ > Public API is intentionally strict, but may evolve before v1.0.0.
10
+
11
+ `create-sdk` is a CLI tool that bootstraps a modern TypeScript SDK with an
12
+ **ESM-first setup**, **API Extractor**, and a **CI-ready structure** —
13
+ so you can focus on features, not boilerplate.
14
+
15
+ ---
16
+
17
+ ## Features
18
+
19
+ - **Interactive CLI** (or fully automated with flags)
20
+ - 📦 **ESM-first TypeScript setup**
21
+ - 🔒 **Strict public API** enforced by API Extractor
22
+ - 🧪 **CI-verified & smoke-tested**
23
+ - 🧱 Clean, scalable project structure
24
+ - 🚫 No hidden magic, no vendor lock-in
25
+
26
+ ---
27
+
28
+ ## 🚀 Quick start
29
+
30
+ This creates a clean SDK with a strict public API enforced by API Extractor.
31
+
32
+ ```bash
33
+ npx @morda-dev/create-sdk my-sdk
34
+ cd my-sdk
35
+ npm install
36
+ npm run build
37
+ ```
38
+
39
+ That’s it. You now have a solid, production-oriented SDK foundation.
40
+
41
+
42
+ ## 📘 Example SDK
43
+
44
+ See a minimal, production-style SDK generated with create-sdk:
45
+ 👉 https://github.com/mordaHQ/example-sdk
46
+
47
+ This example demonstrates:
48
+
49
+ - a single, explicit public API entrypoint (src/index.ts)
50
+ - API Extractor–controlled surface
51
+ - ESM-first configuration
52
+ - CI-ready setup
53
+ - safe evolution without accidental breaking changes
54
+
55
+ Use it as a reference for structuring real-world SDKs.
56
+
57
+ ## Why create-sdk?
58
+
59
+ Most SDK starters fall into one of two traps:
60
+
61
+ ❌ too minimal — no real production setup
62
+
63
+ ❌ too complex — opinionated, hard to extend
64
+
65
+ **create-sdk** sits in the middle:
66
+
67
+ Strict where it matters. Flexible where it should be.
68
+
69
+ You get:
70
+ - a clearly defined public API
71
+ - predictable build & release flow
72
+ - freedom to grow the SDK your way
73
+
74
+ ### 🧩 What’s included
75
+
76
+ - TypeScript project with strict configuration
77
+ - API Extractor setup
78
+ - Ready-to-use CI workflow
79
+ - Clean module-based structure
80
+ - Example public API and types
81
+
82
+ ## 🛠 CLI options
83
+ ```bash
84
+ npx @morda-dev/create-sdk my-sdk --yes
85
+ ```
86
+
87
+ ### Options
88
+
89
+ - `--yes` — skip all prompts
90
+ - `--dry-run` — preview what would be generated without creating files
91
+ - `--force` — overwrite existing directory if it exists
92
+ - `--no-install` — skip dependency installation
93
+ - `--no-git` skip git initialization
94
+
95
+ ### Examples
96
+
97
+ ```bash
98
+ npx @morda-dev/create-sdk my-sdk --yes
99
+ npx @morda-dev/create-sdk my-sdk --dry-run
100
+ npx @morda-dev/create-sdk my-sdk --force --no-install
101
+ ```
102
+
103
+ ### 📦 Requirements
104
+ - Node.js >= 18
105
+ - npm / pnpm / yarn
106
+
107
+
108
+ ### 🧭 Roadmap
109
+
110
+ - Custom templates
111
+ - Optional monorepo mode
112
+ - Plugin system
113
+ - SDK release automation helpers
114
+
115
+
116
+
117
+ ## 🤝 Contributing
118
+
119
+ Issues and PRs are welcome.
120
+
121
+ This project is intentionally kept small, clean, and well-documented.
122
+
123
+ ## 📄 License
124
+
125
+ ISC
126
+
127
+
128
+ ---
package/bin/create-sdk.js CHANGED
@@ -1,261 +1,95 @@
1
- #!/usr/bin/env node
2
-
3
- import fs from "fs";
4
- import path from "path";
5
- import process from "process";
6
- import { fileURLToPath } from "url";
7
- import { execSync } from "child_process";
8
- import prompts from "prompts";
9
-
10
- /* ============================================================================
11
- * Environment checks
12
- * ============================================================================
13
- */
14
-
15
- const REQUIRED_NODE_MAJOR = 18;
16
- const CURRENT_NODE_MAJOR = Number(process.versions.node.split(".")[0]);
17
-
18
- if (CURRENT_NODE_MAJOR < REQUIRED_NODE_MAJOR) {
19
- console.error(
20
- `❌ Node.js ${REQUIRED_NODE_MAJOR}+ is required.\n` +
21
- ` You are using Node.js ${process.versions.node}`
22
- );
23
- process.exit(1);
24
- }
25
-
26
- /* ============================================================================
27
- * CLI args & flags
28
- * ============================================================================
29
- */
30
-
31
- const rawArgs = process.argv.slice(2);
32
-
33
- const flags = {
34
- yes: rawArgs.includes("--yes"),
35
- noGit: rawArgs.includes("--no-git"),
36
- noInstall: rawArgs.includes("--no-install"),
37
- };
38
-
39
- if (rawArgs.includes("--version") || rawArgs.includes("-v")) {
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");
73
-
74
- /* ============================================================================
75
- * Project name & interactive flow
76
- * ============================================================================
77
- */
78
-
79
- const argProjectName = rawArgs.find((a) => !a.startsWith("-"));
80
-
81
- let projectName;
82
- let initGit = !flags.noGit;
83
- let installDeps = !flags.noInstall;
84
-
85
- if (flags.yes && argProjectName) {
86
- projectName = argProjectName;
87
- } else if (!argProjectName) {
88
- const answers = await prompts(
89
- [
90
- {
91
- type: "text",
92
- name: "name",
93
- message: "Project name:",
94
- validate: (v) => (v ? true : "Project name is required"),
95
- },
96
- {
97
- type: "confirm",
98
- name: "initGit",
99
- message: "Initialize git repository?",
100
- initial: true,
101
- },
102
- {
103
- type: "confirm",
104
- name: "installDeps",
105
- message: "Install dependencies now?",
106
- initial: false,
107
- },
108
- ],
109
- {
110
- onCancel: () => {
111
- console.log("\n❌ Cancelled");
112
- process.exit(1);
113
- },
114
- }
115
- );
116
-
117
- projectName = answers.name;
118
- initGit = answers.initGit;
119
- installDeps = answers.installDeps;
120
- } else {
121
- projectName = argProjectName;
122
- }
123
-
124
- if (!projectName) {
125
- console.error("❌ Project name is required");
126
- process.exit(1);
127
- }
128
-
129
- /* ============================================================================
130
- * Target directory
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
166
- * ============================================================================
167
- */
168
-
169
- const pkgPath = path.join(targetDir, "package.json");
170
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
171
-
172
- pkg.name = projectName;
173
- pkg.version = "0.1.0";
174
- delete pkg.private;
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
- }
1
+ #!/usr/bin/env node
2
+ import process from "process";
3
+ import prompts from "prompts";
4
+ import { scaffold } from "../src/cli/scaffold.js";
5
+
6
+ /* ============================================================================
7
+ * Node version check
8
+ * ============================================================================
9
+ */
10
+ const REQUIRED_NODE_MAJOR = 18;
11
+ const CURRENT_NODE_MAJOR = Number(process.versions.node.split(".")[0]);
12
+
13
+ if (CURRENT_NODE_MAJOR < REQUIRED_NODE_MAJOR) {
14
+ console.error(
15
+ `❌ Node.js ${REQUIRED_NODE_MAJOR}+ is required.\n` +
16
+ ` You are using Node.js ${process.versions.node}`
17
+ );
18
+ process.exit(1);
19
+ }
20
+
21
+ /* ============================================================================
22
+ * Args parsing
23
+ * ============================================================================
24
+ */
25
+ const rawArgs = process.argv.slice(2);
26
+
27
+ const flags = {
28
+ yes: rawArgs.includes("--yes"),
29
+ noGit: rawArgs.includes("--no-git"),
30
+ noInstall: rawArgs.includes("--no-install"),
31
+ dryRun: rawArgs.includes("--dry-run"),
32
+ force: rawArgs.includes("--force"),
33
+ };
34
+
35
+ const projectName = rawArgs.find((a) => !a.startsWith("-"));
36
+
37
+ /* ============================================================================
38
+ * Interactive mode
39
+ * ============================================================================
40
+ */
41
+ let name = projectName;
42
+ let initGit = !flags.noGit;
43
+ let installDeps = !flags.noInstall;
44
+
45
+ if (!flags.yes && !name) {
46
+ const answers = await prompts(
47
+ [
48
+ {
49
+ type: "text",
50
+ name: "name",
51
+ message: "Project name:",
52
+ validate: (v) => (v ? true : "Project name is required"),
53
+ },
54
+ {
55
+ type: "confirm",
56
+ name: "initGit",
57
+ message: "Initialize git repository?",
58
+ initial: true,
59
+ },
60
+ {
61
+ type: "confirm",
62
+ name: "installDeps",
63
+ message: "Install dependencies now?",
64
+ initial: false,
65
+ },
66
+ ],
67
+ {
68
+ onCancel: () => {
69
+ console.log("\n❌ Cancelled");
70
+ process.exit(1);
71
+ },
72
+ }
73
+ );
74
+
75
+ name = answers.name;
76
+ initGit = answers.initGit;
77
+ installDeps = answers.installDeps;
78
+ }
79
+
80
+ if (!name) {
81
+ console.error("❌ Project name is required");
82
+ process.exit(1);
83
+ }
84
+
85
+ /* ============================================================================
86
+ * Execute scaffold
87
+ * ============================================================================
88
+ */
89
+ await scaffold({
90
+ projectName: name,
91
+ initGit,
92
+ installDeps,
93
+ dryRun: flags.dryRun,
94
+ force: flags.force,
95
+ });
package/package.json CHANGED
@@ -1,15 +1,18 @@
1
1
  {
2
2
  "name": "@morda-dev/create-sdk",
3
- "version": "1.1.3",
3
+ "version": "1.2.0",
4
4
  "description": "CLI to scaffold a production-ready TypeScript SDK with a strict public API",
5
+ "license": "ISC",
5
6
  "type": "module",
6
7
  "bin": {
7
8
  "create-sdk": "./bin/create-sdk.js"
8
9
  },
9
10
  "files": [
10
11
  "bin",
12
+ "src",
11
13
  "template",
12
- "README.md"
14
+ "README.md",
15
+ "LICENSE"
13
16
  ],
14
17
  "engines": {
15
18
  "node": ">=18"
@@ -29,21 +32,24 @@
29
32
  "cli",
30
33
  "scaffold",
31
34
  "api-extractor",
32
- "library"
35
+ "library",
36
+ "esm",
37
+ "developer-tools"
33
38
  ],
34
39
  "scripts": {
35
- "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
- "changeset": "changeset",
37
- "version": "changeset version",
38
- "release": "changeset publish"
40
+ "test": "vitest run",
41
+ "test:watch": "vitest",
42
+ "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 }))\""
39
43
  },
40
44
  "dependencies": {
41
45
  "prompts": "^2.4.2"
42
46
  },
43
47
  "devDependencies": {
44
- "@changesets/cli": "^2.29.8",
45
48
  "@types/node": "^18.19.0",
49
+ "vitest": "^1.6.0",
50
+ "execa": "^9.6.1",
51
+ "fs-extra": "^11.3.3",
52
+ "tmp": "^0.2.5",
46
53
  "undici-types": "^7.16.0"
47
- },
48
- "license": "ISC"
54
+ }
49
55
  }
@@ -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
+ }