@morda-dev/create-sdk 1.2.1 → 1.3.10
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 +16 -0
- package/bin/create-sdk.js +51 -11
- package/package.json +16 -3
- package/src/cli/scaffold.js +20 -16
- package/template/package.json +4 -1
- package/template/src/index.ts +24 -3
- package/template/src/modules/user.ts +18 -19
package/README.md
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# create-sdk
|
|
2
|
+
# create-sdk
|
|
2
3
|

|
|
3
4
|

|
|
4
5
|

|
|
@@ -8,6 +9,8 @@
|
|
|
8
9
|
> ⚠️ Early-stage project (v0.x).
|
|
9
10
|
> Public API is intentionally strict, but may evolve before v1.0.0.
|
|
10
11
|
|
|
12
|
+
> **If it’s not exported from `src/index.ts`, it’s not part of the public API.**
|
|
13
|
+
|
|
11
14
|
`create-sdk` is a CLI tool that bootstraps a modern TypeScript SDK with an
|
|
12
15
|
**ESM-first setup**, **API Extractor**, and a **CI-ready structure** —
|
|
13
16
|
so you can focus on features, not boilerplate.
|
|
@@ -38,6 +41,19 @@ npm run build
|
|
|
38
41
|
|
|
39
42
|
That’s it. You now have a solid, production-oriented SDK foundation.
|
|
40
43
|
|
|
44
|
+
## 📁 Project structure
|
|
45
|
+
|
|
46
|
+
The generated SDK includes:
|
|
47
|
+
|
|
48
|
+
- `src/index.ts` — the single public API entrypoint
|
|
49
|
+
- `src/modules/*` — internal implementation modules
|
|
50
|
+
- `src/types/*` — public & internal types
|
|
51
|
+
- `dist/` — compiled output (ESM)
|
|
52
|
+
- `api-extractor.json` — public API surface control
|
|
53
|
+
- CI workflow for build & API checks
|
|
54
|
+
|
|
55
|
+
Only symbols exported from `src/index.ts` are considered public and stable.
|
|
56
|
+
|
|
41
57
|
|
|
42
58
|
## 📘 Example SDK
|
|
43
59
|
|
package/bin/create-sdk.js
CHANGED
|
@@ -13,7 +13,7 @@ const CURRENT_NODE_MAJOR = Number(process.versions.node.split(".")[0]);
|
|
|
13
13
|
if (CURRENT_NODE_MAJOR < REQUIRED_NODE_MAJOR) {
|
|
14
14
|
console.error(
|
|
15
15
|
`❌ Node.js ${REQUIRED_NODE_MAJOR}+ is required.\n` +
|
|
16
|
-
|
|
16
|
+
` You are using Node.js ${process.versions.node}`
|
|
17
17
|
);
|
|
18
18
|
process.exit(1);
|
|
19
19
|
}
|
|
@@ -35,7 +35,27 @@ const flags = {
|
|
|
35
35
|
const projectName = rawArgs.find((a) => !a.startsWith("-"));
|
|
36
36
|
|
|
37
37
|
/* ============================================================================
|
|
38
|
-
*
|
|
38
|
+
* Help (EARLY EXIT, SNAPSHOT SAFE)
|
|
39
|
+
* ============================================================================
|
|
40
|
+
*/
|
|
41
|
+
if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
|
|
42
|
+
console.log(
|
|
43
|
+
`Usage:
|
|
44
|
+
create-sdk <project-name> [options]
|
|
45
|
+
|
|
46
|
+
Options:
|
|
47
|
+
--yes Skip all prompts
|
|
48
|
+
--no-git Do not initialize git repository
|
|
49
|
+
--no-install Do not install dependencies
|
|
50
|
+
--dry-run Show what would be done without writing files
|
|
51
|
+
--force Overwrite existing directory
|
|
52
|
+
--help, -h Show this help message`
|
|
53
|
+
);
|
|
54
|
+
process.exit(0);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* ============================================================================
|
|
58
|
+
* Interactive / Non-interactive resolution
|
|
39
59
|
* ============================================================================
|
|
40
60
|
*/
|
|
41
61
|
let name = projectName;
|
|
@@ -66,7 +86,7 @@ if (!flags.yes && !name) {
|
|
|
66
86
|
],
|
|
67
87
|
{
|
|
68
88
|
onCancel: () => {
|
|
69
|
-
console.
|
|
89
|
+
console.error("❌ Cancelled");
|
|
70
90
|
process.exit(1);
|
|
71
91
|
},
|
|
72
92
|
}
|
|
@@ -77,19 +97,39 @@ if (!flags.yes && !name) {
|
|
|
77
97
|
installDeps = answers.installDeps;
|
|
78
98
|
}
|
|
79
99
|
|
|
100
|
+
/* ============================================================================
|
|
101
|
+
* Validation
|
|
102
|
+
* ============================================================================
|
|
103
|
+
*/
|
|
80
104
|
if (!name) {
|
|
81
105
|
console.error("❌ Project name is required");
|
|
82
106
|
process.exit(1);
|
|
83
107
|
}
|
|
84
108
|
|
|
85
109
|
/* ============================================================================
|
|
86
|
-
* Execute scaffold
|
|
110
|
+
* Execute scaffold (CLI CONTRACT OWNER)
|
|
87
111
|
* ============================================================================
|
|
88
112
|
*/
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
113
|
+
try {
|
|
114
|
+
await scaffold({
|
|
115
|
+
projectName: name,
|
|
116
|
+
initGit,
|
|
117
|
+
installDeps,
|
|
118
|
+
dryRun: flags.dryRun,
|
|
119
|
+
force: flags.force,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (flags.dryRun) {
|
|
123
|
+
console.log("ℹ️ Dry run completed. No files were written.");
|
|
124
|
+
} else {
|
|
125
|
+
console.log("🎉 SDK scaffolded successfully!");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
process.exit(0);
|
|
129
|
+
} catch (err) {
|
|
130
|
+
const message =
|
|
131
|
+
err instanceof Error ? err.message : "Unexpected error occurred";
|
|
132
|
+
|
|
133
|
+
console.error(`❌ ${message}`);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morda-dev/create-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.10",
|
|
4
4
|
"description": "CLI to scaffold a production-ready TypeScript SDK with a strict public API",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"type": "module",
|
|
7
|
+
|
|
7
8
|
"bin": {
|
|
8
9
|
"create-sdk": "bin/create-sdk.js"
|
|
9
10
|
},
|
|
11
|
+
|
|
10
12
|
"files": [
|
|
11
13
|
"bin",
|
|
12
14
|
"src",
|
|
@@ -14,17 +16,22 @@
|
|
|
14
16
|
"README.md",
|
|
15
17
|
"LICENSE"
|
|
16
18
|
],
|
|
19
|
+
|
|
17
20
|
"engines": {
|
|
18
21
|
"node": ">=18"
|
|
19
22
|
},
|
|
23
|
+
|
|
20
24
|
"repository": {
|
|
21
25
|
"type": "git",
|
|
22
26
|
"url": "git+https://github.com/mordaHQ/create-sdk.git"
|
|
23
27
|
},
|
|
28
|
+
|
|
24
29
|
"homepage": "https://github.com/mordaHQ/create-sdk",
|
|
30
|
+
|
|
25
31
|
"bugs": {
|
|
26
32
|
"url": "https://github.com/mordaHQ/create-sdk/issues"
|
|
27
33
|
},
|
|
34
|
+
|
|
28
35
|
"keywords": [
|
|
29
36
|
"create-sdk",
|
|
30
37
|
"typescript",
|
|
@@ -36,20 +43,26 @@
|
|
|
36
43
|
"esm",
|
|
37
44
|
"developer-tools"
|
|
38
45
|
],
|
|
46
|
+
|
|
39
47
|
"scripts": {
|
|
40
48
|
"test": "vitest run",
|
|
41
49
|
"test:watch": "vitest",
|
|
42
50
|
"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 }))\""
|
|
43
51
|
},
|
|
52
|
+
|
|
44
53
|
"dependencies": {
|
|
45
54
|
"prompts": "^2.4.2"
|
|
46
55
|
},
|
|
56
|
+
|
|
47
57
|
"devDependencies": {
|
|
48
58
|
"@types/node": "^18.19.0",
|
|
49
|
-
"vitest": "^1.6.0",
|
|
50
59
|
"execa": "^9.6.1",
|
|
51
60
|
"fs-extra": "^11.3.3",
|
|
52
61
|
"tmp": "^0.2.5",
|
|
53
|
-
"
|
|
62
|
+
"vitest": "^1.6.0"
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
"publishConfig": {
|
|
66
|
+
"access": "public"
|
|
54
67
|
}
|
|
55
68
|
}
|
package/src/cli/scaffold.js
CHANGED
|
@@ -8,6 +8,10 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
8
8
|
const __dirname = path.dirname(__filename);
|
|
9
9
|
const templateDir = path.resolve(__dirname, "../../template");
|
|
10
10
|
|
|
11
|
+
const isTest =
|
|
12
|
+
process.env.NODE_ENV === "test" ||
|
|
13
|
+
process.env.VITEST === "true";
|
|
14
|
+
|
|
11
15
|
export async function scaffold({
|
|
12
16
|
projectName,
|
|
13
17
|
initGit = true,
|
|
@@ -26,7 +30,14 @@ export async function scaffold({
|
|
|
26
30
|
const targetDir = path.resolve(process.cwd(), dirName);
|
|
27
31
|
|
|
28
32
|
// ─────────────────────────────────────────────
|
|
29
|
-
//
|
|
33
|
+
// 🔒 CLI CONTRACT — EARLY FAILURE
|
|
34
|
+
// ─────────────────────────────────────────────
|
|
35
|
+
if (fs.existsSync(targetDir) && !force) {
|
|
36
|
+
throw new Error(`Directory "${dirName}" already exists`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ─────────────────────────────────────────────
|
|
40
|
+
// 🧪 DRY-RUN — NO SIDE EFFECTS
|
|
30
41
|
// ─────────────────────────────────────────────
|
|
31
42
|
if (dryRun) {
|
|
32
43
|
console.log("🧪 [dry-run] Creating SDK:", projectName);
|
|
@@ -35,26 +46,22 @@ export async function scaffold({
|
|
|
35
46
|
if (initGit) console.log("🧪 [dry-run] Would init git");
|
|
36
47
|
if (installDeps) console.log("🧪 [dry-run] Would install dependencies");
|
|
37
48
|
console.log("🧪 [dry-run] Done (NO filesystem changes)");
|
|
38
|
-
return;
|
|
49
|
+
return { targetDir };
|
|
39
50
|
}
|
|
40
51
|
|
|
41
52
|
// ─────────────────────────────────────────────
|
|
42
|
-
// REAL MODE —
|
|
53
|
+
// REAL MODE — FS MUTATIONS
|
|
43
54
|
// ─────────────────────────────────────────────
|
|
44
55
|
if (!fs.existsSync(templateDir)) {
|
|
45
56
|
throw new Error(`Template directory not found: ${templateDir}`);
|
|
46
57
|
}
|
|
47
58
|
|
|
48
|
-
if (fs.existsSync(targetDir)) {
|
|
49
|
-
if (!force) {
|
|
50
|
-
throw new Error(`Directory "${dirName}" already exists`);
|
|
51
|
-
}
|
|
59
|
+
if (fs.existsSync(targetDir) && force) {
|
|
52
60
|
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
53
61
|
}
|
|
54
62
|
|
|
55
63
|
console.log("🚀 Creating SDK:", projectName);
|
|
56
64
|
|
|
57
|
-
// copy template
|
|
58
65
|
copyDir(templateDir, targetDir);
|
|
59
66
|
|
|
60
67
|
// package.json
|
|
@@ -63,11 +70,8 @@ export async function scaffold({
|
|
|
63
70
|
|
|
64
71
|
pkg.name = projectName;
|
|
65
72
|
pkg.version = "0.1.0";
|
|
66
|
-
|
|
67
|
-
// шаблон НЕ должен быть private
|
|
68
73
|
delete pkg.private;
|
|
69
74
|
|
|
70
|
-
// exports добавляются ТОЛЬКО тут
|
|
71
75
|
pkg.main = "./dist/index.js";
|
|
72
76
|
pkg.types = "./dist/index.d.ts";
|
|
73
77
|
pkg.exports = {
|
|
@@ -88,20 +92,20 @@ export async function scaffold({
|
|
|
88
92
|
fs.writeFileSync(readmePath, updated);
|
|
89
93
|
}
|
|
90
94
|
|
|
91
|
-
// git
|
|
92
|
-
if (initGit) {
|
|
95
|
+
// git (disabled in tests)
|
|
96
|
+
if (initGit && !isTest) {
|
|
93
97
|
try {
|
|
94
98
|
execSync("git init", { cwd: targetDir, stdio: "ignore" });
|
|
95
99
|
} catch {}
|
|
96
100
|
}
|
|
97
101
|
|
|
98
|
-
// deps
|
|
99
|
-
if (installDeps) {
|
|
102
|
+
// deps (disabled in tests)
|
|
103
|
+
if (installDeps && !isTest) {
|
|
100
104
|
execSync("npm install", {
|
|
101
105
|
cwd: targetDir,
|
|
102
106
|
stdio: "inherit",
|
|
103
107
|
});
|
|
104
108
|
}
|
|
105
109
|
|
|
106
|
-
|
|
110
|
+
return { targetDir };
|
|
107
111
|
}
|
package/template/package.json
CHANGED
package/template/src/index.ts
CHANGED
|
@@ -17,9 +17,30 @@
|
|
|
17
17
|
export type { User, ApiResult } from "./types/user.js";
|
|
18
18
|
|
|
19
19
|
/* ============================================================================
|
|
20
|
-
* Functions (Public)
|
|
20
|
+
* Values & Functions (Public)
|
|
21
21
|
* ============================================================================
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
/**
|
|
25
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Public user entity.
|
|
26
|
+
*
|
|
27
|
+
* Exported explicitly to allow direct consumption:
|
|
28
|
+
* `import { user } from "sdk"`
|
|
29
|
+
*
|
|
30
|
+
* @public
|
|
31
|
+
*/
|
|
32
|
+
export { user } from "./modules/user.js";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Returns a user entity.
|
|
36
|
+
*
|
|
37
|
+
* @public
|
|
38
|
+
*/
|
|
39
|
+
export { getUser } from "./modules/user.js";
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Example aggregate function.
|
|
43
|
+
*
|
|
44
|
+
* @public
|
|
45
|
+
*/
|
|
46
|
+
export { sumAges } from "./modules/user.js";
|
|
@@ -1,31 +1,30 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { User } from "../types/user.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
+
* Public user constant
|
|
5
|
+
*
|
|
4
6
|
* @public
|
|
5
|
-
* Get a user by id
|
|
6
7
|
*/
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
};
|
|
13
|
-
}
|
|
8
|
+
export const user: User = {
|
|
9
|
+
id: 1,
|
|
10
|
+
name: "Alice",
|
|
11
|
+
age: 30,
|
|
12
|
+
};
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
};
|
|
14
|
+
/**
|
|
15
|
+
* Returns a user entity.
|
|
16
|
+
*
|
|
17
|
+
* @public
|
|
18
|
+
*/
|
|
19
|
+
export function getUser(): User {
|
|
20
|
+
return user;
|
|
23
21
|
}
|
|
24
22
|
|
|
25
23
|
/**
|
|
24
|
+
* Example aggregate function.
|
|
25
|
+
*
|
|
26
26
|
* @public
|
|
27
|
-
* Sum ages of users
|
|
28
27
|
*/
|
|
29
28
|
export function sumAges(users: User[]): number {
|
|
30
|
-
return users.reduce((
|
|
29
|
+
return users.reduce((a, u) => a + u.age, 0);
|
|
31
30
|
}
|