@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 CHANGED
@@ -1,4 +1,5 @@
1
1
  # create-sdk
2
+ # create-sdk
2
3
  ![npm](https://img.shields.io/npm/v/@morda-dev/create-sdk)
3
4
  ![npm downloads](https://img.shields.io/npm/dm/@morda-dev/create-sdk)
4
5
  ![CI](https://github.com/mordaHQ/create-sdk/actions/workflows/ci.yml/badge.svg)
@@ -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
- ` You are using Node.js ${process.versions.node}`
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
- * Interactive mode
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.log("\n❌ Cancelled");
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
- await scaffold({
90
- projectName: name,
91
- initGit,
92
- installDeps,
93
- dryRun: flags.dryRun,
94
- force: flags.force,
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.2.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
- "undici-types": "^7.16.0"
62
+ "vitest": "^1.6.0"
63
+ },
64
+
65
+ "publishConfig": {
66
+ "access": "public"
54
67
  }
55
68
  }
@@ -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
- // 🧪 DRY-RUNABSOLUTE NO-SIDE-EFFECT MODE
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 — FILESYSTEM IS TOUCHED ONLY HERE
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
- console.log("🎉 SDK scaffolded successfully!");
110
+ return { targetDir };
107
111
  }
@@ -22,7 +22,10 @@
22
22
  },
23
23
 
24
24
  "files": [
25
- "dist"
25
+ "dist",
26
+ "README.md",
27
+ "LICENSE",
28
+ "package.json"
26
29
  ],
27
30
 
28
31
  "repository": {
@@ -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
- /** @public */
25
- export { getUser, sumAges } from "./modules/user.js";
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 { ApiResult, User } from "../types/user.js";
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 function getUser(id: number): ApiResult<User> {
8
- if (id <= 0) {
9
- return {
10
- success: false,
11
- error: "Invalid user id",
12
- };
13
- }
8
+ export const user: User = {
9
+ id: 1,
10
+ name: "Alice",
11
+ age: 30,
12
+ };
14
13
 
15
- return {
16
- success: true,
17
- data: {
18
- id,
19
- name: "John Doe",
20
- age: 30,
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((sum, user) => sum + user.age, 0);
29
+ return users.reduce((a, u) => a + u.age, 0);
31
30
  }