create-stb 1.0.5 → 1.1.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.
@@ -1,15 +1,77 @@
1
1
  #!/usr/bin/env node
2
+ /**
3
+ * Sanitizes a file path to prevent command injection attacks.
4
+ * Validates input, blocks dangerous characters, and returns normalized absolute path.
5
+ * @param inputPath - The path to sanitize
6
+ * @returns Sanitized absolute path
7
+ * @throws Error if path is invalid or contains dangerous characters
8
+ */
9
+ export declare function sanitizePath(inputPath: string): string;
10
+ /**
11
+ * Checks if the current Node.js version meets the minimum required version.
12
+ * @param minMajor - Minimum required major version (default: 20)
13
+ * @throws Error if Node.js version is below the minimum required
14
+ */
2
15
  export declare function checkNodeVersion(minMajor?: number): void;
16
+ /**
17
+ * Verifies that Git is installed and available on the system.
18
+ * @throws Error if Git is not installed or cannot be executed
19
+ */
20
+ export declare function checkGitInstalled(): void;
21
+ /**
22
+ * Clones the create-stb repository to a temporary directory using sparse checkout
23
+ * to retrieve only the serverless template folder.
24
+ * @param tempDir - Temporary directory path for cloning
25
+ * @returns Path to the cloned serverless template directory
26
+ * @throws Error if git clone fails or serverless folder is not found
27
+ */
28
+ export declare function cloneBoilerplate(tempDir: string): string;
29
+ /**
30
+ * Creates a new project directory, ensuring it doesn't exist or is empty.
31
+ * @param projectPath - Path to the project directory to create
32
+ * @throws Error if directory exists and is not empty
33
+ */
3
34
  export declare function createProjectDirectory(projectPath: string): void;
35
+ /**
36
+ * Recursively copies all files and directories from source to destination,
37
+ * including dotfiles and nested directories.
38
+ * @param src - Source directory path
39
+ * @param dest - Destination directory path
40
+ */
4
41
  export declare function copyDir(src: string, dest: string): void;
42
+ /**
43
+ * Removes a temporary directory and all its contents.
44
+ * Does not throw if directory doesn't exist.
45
+ * @param tempDir - Path to temporary directory to remove
46
+ */
47
+ export declare function cleanupTemp(tempDir: string): void;
48
+ /**
49
+ * Updates the package.json file with the new project name and description.
50
+ * @param projectPath - Path to the project directory
51
+ * @param projectName - Name for the new project
52
+ */
5
53
  export declare function updatePackageJson(projectPath: string, projectName: string): void;
54
+ /**
55
+ * Updates the serverless.yml file with the new service name.
56
+ * @param projectPath - Path to the project directory
57
+ * @param projectName - Name for the new service
58
+ */
6
59
  export declare function updateServerlessYml(projectPath: string, projectName: string): void;
60
+ /**
61
+ * Main CLI entry point. Orchestrates the entire project scaffolding process.
62
+ * @throws Error if any step fails during project creation
63
+ */
7
64
  export declare function main(): Promise<void>;
8
65
  declare const _default: {
9
66
  checkNodeVersion: typeof checkNodeVersion;
67
+ checkGitInstalled: typeof checkGitInstalled;
68
+ sanitizePath: typeof sanitizePath;
69
+ cloneBoilerplate: typeof cloneBoilerplate;
10
70
  createProjectDirectory: typeof createProjectDirectory;
11
71
  copyDir: typeof copyDir;
72
+ cleanupTemp: typeof cleanupTemp;
12
73
  updatePackageJson: typeof updatePackageJson;
74
+ updateServerlessYml: typeof updateServerlessYml;
13
75
  main: typeof main;
14
76
  };
15
77
  export default _default;
@@ -4,16 +4,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.sanitizePath = sanitizePath;
7
8
  exports.checkNodeVersion = checkNodeVersion;
9
+ exports.checkGitInstalled = checkGitInstalled;
10
+ exports.cloneBoilerplate = cloneBoilerplate;
8
11
  exports.createProjectDirectory = createProjectDirectory;
9
12
  exports.copyDir = copyDir;
13
+ exports.cleanupTemp = cleanupTemp;
10
14
  exports.updatePackageJson = updatePackageJson;
11
15
  exports.updateServerlessYml = updateServerlessYml;
12
16
  exports.main = main;
13
17
  const path_1 = __importDefault(require("path"));
14
18
  const fs_1 = __importDefault(require("fs"));
15
19
  const child_process_1 = require("child_process");
16
- // Progress display helpers
20
+ const os_1 = __importDefault(require("os"));
17
21
  function writeLine(msg) {
18
22
  const width = process.stdout.columns || 80;
19
23
  process.stdout.write(`\r${" ".repeat(width)}\r${msg}`);
@@ -28,13 +32,74 @@ function step(label) {
28
32
  function doneStep() {
29
33
  clearLine();
30
34
  }
31
- // Ensure required Node version
35
+ /**
36
+ * Sanitizes a file path to prevent command injection attacks.
37
+ * Validates input, blocks dangerous characters, and returns normalized absolute path.
38
+ * @param inputPath - The path to sanitize
39
+ * @returns Sanitized absolute path
40
+ * @throws Error if path is invalid or contains dangerous characters
41
+ */
42
+ function sanitizePath(inputPath) {
43
+ if (typeof inputPath !== "string" || !inputPath.trim()) {
44
+ throw new Error("Path must be a non-empty string");
45
+ }
46
+ const trimmed = inputPath.trim();
47
+ // Block input that starts with '-' or '--'
48
+ if (trimmed.startsWith('-') || trimmed.startsWith('--')) {
49
+ throw new Error("Invalid path: must not start with '-' or '--'");
50
+ }
51
+ // Block dangerous shell metacharacters
52
+ if (/[`$|;&<>\0]/.test(trimmed)) {
53
+ throw new Error("Invalid path: contains dangerous characters");
54
+ }
55
+ return path_1.default.normalize(path_1.default.resolve(trimmed));
56
+ }
57
+ /**
58
+ * Checks if the current Node.js version meets the minimum required version.
59
+ * @param minMajor - Minimum required major version (default: 20)
60
+ * @throws Error if Node.js version is below the minimum required
61
+ */
32
62
  function checkNodeVersion(minMajor = 20) {
33
63
  const [major] = process.version.replace("v", "").split(".");
34
64
  if (Number(major) < minMajor)
35
65
  throw new Error(`Node.js v${minMajor}+ required`);
36
66
  }
37
- // Create an empty directory (or error if not empty)
67
+ /**
68
+ * Verifies that Git is installed and available on the system.
69
+ * @throws Error if Git is not installed or cannot be executed
70
+ */
71
+ function checkGitInstalled() {
72
+ try {
73
+ (0, child_process_1.execFileSync)("git", ["--version"], { stdio: "ignore" });
74
+ }
75
+ catch {
76
+ throw new Error("Git is not installed. Please install git and try again.");
77
+ }
78
+ }
79
+ /**
80
+ * Clones the create-stb repository to a temporary directory using sparse checkout
81
+ * to retrieve only the serverless template folder.
82
+ * @param tempDir - Temporary directory path for cloning
83
+ * @returns Path to the cloned serverless template directory
84
+ * @throws Error if git clone fails or serverless folder is not found
85
+ */
86
+ function cloneBoilerplate(tempDir) {
87
+ const repoUrl = "https://github.com/SamNewhouse/create-stb.git";
88
+ const safeTempDir = sanitizePath(tempDir);
89
+ const clonePath = path_1.default.join(safeTempDir, "create-stb-temp");
90
+ const safeClonePath = sanitizePath(clonePath);
91
+ (0, child_process_1.execFileSync)("git", ["clone", "--depth", "1", "--filter=blob:none", "--sparse", repoUrl, safeClonePath], { stdio: "ignore" });
92
+ (0, child_process_1.execFileSync)("git", ["sparse-checkout", "set", "serverless"], {
93
+ cwd: safeClonePath,
94
+ stdio: "ignore",
95
+ });
96
+ return path_1.default.join(safeClonePath, "serverless");
97
+ }
98
+ /**
99
+ * Creates a new project directory, ensuring it doesn't exist or is empty.
100
+ * @param projectPath - Path to the project directory to create
101
+ * @throws Error if directory exists and is not empty
102
+ */
38
103
  function createProjectDirectory(projectPath) {
39
104
  if (fs_1.default.existsSync(projectPath)) {
40
105
  if (fs_1.default.readdirSync(projectPath).length === 0)
@@ -43,7 +108,12 @@ function createProjectDirectory(projectPath) {
43
108
  }
44
109
  fs_1.default.mkdirSync(projectPath, { recursive: true });
45
110
  }
46
- // Recursively copy everything, including dotfiles and subfolders
111
+ /**
112
+ * Recursively copies all files and directories from source to destination,
113
+ * including dotfiles and nested directories.
114
+ * @param src - Source directory path
115
+ * @param dest - Destination directory path
116
+ */
47
117
  function copyDir(src, dest) {
48
118
  if (!fs_1.default.existsSync(dest))
49
119
  fs_1.default.mkdirSync(dest, { recursive: true });
@@ -58,7 +128,21 @@ function copyDir(src, dest) {
58
128
  }
59
129
  }
60
130
  }
61
- // Only update basic project info in package.json
131
+ /**
132
+ * Removes a temporary directory and all its contents.
133
+ * Does not throw if directory doesn't exist.
134
+ * @param tempDir - Path to temporary directory to remove
135
+ */
136
+ function cleanupTemp(tempDir) {
137
+ if (fs_1.default.existsSync(tempDir)) {
138
+ fs_1.default.rmSync(tempDir, { recursive: true, force: true });
139
+ }
140
+ }
141
+ /**
142
+ * Updates the package.json file with the new project name and description.
143
+ * @param projectPath - Path to the project directory
144
+ * @param projectName - Name for the new project
145
+ */
62
146
  function updatePackageJson(projectPath, projectName) {
63
147
  const file = path_1.default.join(projectPath, "package.json");
64
148
  if (!fs_1.default.existsSync(file))
@@ -68,6 +152,11 @@ function updatePackageJson(projectPath, projectName) {
68
152
  pkg.description = `${projectName} app description`;
69
153
  fs_1.default.writeFileSync(file, JSON.stringify(pkg, null, 2));
70
154
  }
155
+ /**
156
+ * Updates the serverless.yml file with the new service name.
157
+ * @param projectPath - Path to the project directory
158
+ * @param projectName - Name for the new service
159
+ */
71
160
  function updateServerlessYml(projectPath, projectName) {
72
161
  const serverlessPath = path_1.default.join(projectPath, "serverless.yml");
73
162
  if (!fs_1.default.existsSync(serverlessPath))
@@ -76,7 +165,10 @@ function updateServerlessYml(projectPath, projectName) {
76
165
  content = content.replace(/^service:.*$/m, `service: ${projectName}`);
77
166
  fs_1.default.writeFileSync(serverlessPath, content);
78
167
  }
79
- // Main CLI flow
168
+ /**
169
+ * Main CLI entry point. Orchestrates the entire project scaffolding process.
170
+ * @throws Error if any step fails during project creation
171
+ */
80
172
  async function main() {
81
173
  const name = process.argv[2];
82
174
  if (!name) {
@@ -84,39 +176,59 @@ async function main() {
84
176
  process.exit(1);
85
177
  }
86
178
  const projectPath = path_1.default.resolve(process.cwd(), name);
87
- const boilerplateSrc = path_1.default.resolve(__dirname, "..", "serverless");
88
- step("Checking Node version");
89
- checkNodeVersion();
90
- doneStep();
91
- step("Creating project directory");
92
- createProjectDirectory(projectPath);
93
- doneStep();
94
- step("Copying boilerplate files");
95
- copyDir(boilerplateSrc, projectPath);
96
- doneStep();
97
- step("Updating package.json");
98
- updatePackageJson(projectPath, name);
99
- doneStep();
100
- step("Updating serverless.yml");
101
- updateServerlessYml(projectPath, name);
102
- doneStep();
103
- step("Installing packages");
104
- (0, child_process_1.execSync)("npm install --silent", { cwd: projectPath, stdio: "inherit" });
105
- doneStep();
106
- clearLine();
107
- console.log(`\nInstallation complete\n\nNext steps:\n\n cd ${name}\n npm run offline\n`);
179
+ const tempDir = path_1.default.join(os_1.default.tmpdir(), `create-stb-${Date.now()}`);
180
+ try {
181
+ step("Checking Node version");
182
+ checkNodeVersion();
183
+ doneStep();
184
+ step("Checking Git installation");
185
+ checkGitInstalled();
186
+ doneStep();
187
+ step("Creating project directory");
188
+ createProjectDirectory(projectPath);
189
+ doneStep();
190
+ step("Downloading boilerplate template");
191
+ const boilerplateSrc = cloneBoilerplate(tempDir);
192
+ doneStep();
193
+ step("Copying boilerplate files");
194
+ copyDir(boilerplateSrc, projectPath);
195
+ doneStep();
196
+ step("Cleaning up temporary files");
197
+ cleanupTemp(tempDir);
198
+ doneStep();
199
+ step("Updating package.json");
200
+ updatePackageJson(projectPath, name);
201
+ doneStep();
202
+ step("Updating serverless.yml");
203
+ updateServerlessYml(projectPath, name);
204
+ doneStep();
205
+ step("Installing packages");
206
+ (0, child_process_1.execSync)("npm install --silent", { cwd: projectPath, stdio: "inherit" });
207
+ doneStep();
208
+ clearLine();
209
+ console.log(`\n✅ Installation complete!\n\nNext steps:\n\n cd ${name}\n npm run offline\n`);
210
+ }
211
+ catch (error) {
212
+ cleanupTemp(tempDir);
213
+ throw error;
214
+ }
108
215
  }
109
216
  exports.default = {
110
217
  checkNodeVersion,
218
+ checkGitInstalled,
219
+ sanitizePath,
220
+ cloneBoilerplate,
111
221
  createProjectDirectory,
112
222
  copyDir,
223
+ cleanupTemp,
113
224
  updatePackageJson,
225
+ updateServerlessYml,
114
226
  main,
115
227
  };
116
228
  if (require.main === module) {
117
229
  main().catch((err) => {
118
230
  clearLine();
119
- console.error("Error:", err.message);
231
+ console.error("\n❌ Error:", err.message);
120
232
  process.exit(1);
121
233
  });
122
234
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-stb",
3
- "version": "1.0.5",
3
+ "version": "1.1.0",
4
4
  "description": "A CLI to quickly scaffold a Serverless TypeScript Boilerplate project.",
5
5
  "license": "ISC",
6
6
  "readme": "README.md",
@@ -16,8 +16,7 @@
16
16
  "dist/create-stb.d.ts",
17
17
  "README.md",
18
18
  "LICENSE",
19
- "package.json",
20
- "serverless"
19
+ "package.json"
21
20
  ],
22
21
  "scripts": {
23
22
  "build": "tsc -p tsconfig.json",
@@ -27,7 +26,7 @@
27
26
  "devDependencies": {
28
27
  "@types/jest": "30.0.0",
29
28
  "@types/node": "20.19.25",
30
- "jest": "^30.2.0",
29
+ "jest": "30.2.0",
31
30
  "prettier": "3.6.2",
32
31
  "ts-jest": "29.4.5",
33
32
  "typescript": "5.9.3"
@@ -1,14 +0,0 @@
1
- # AWS credentials (dummy for local, ignored by DynamoDB Local)
2
- AWS_ACCESS_KEY_ID=
3
- AWS_SECRET_ACCESS_KEY=
4
- AWS_REGION=eu-west-2
5
- AWS_REGION_FALLBACK=eu-west-1
6
-
7
- # DynamoDB Setup
8
- DYNAMODB_ENDPOINT=http://localhost:8000
9
-
10
- # If you use other secrets (JWT, third party APIs etc.), add those here too:
11
- JWT_SECRET=
12
-
13
- # Current environment/stage
14
- STAGE=dev
@@ -1,20 +0,0 @@
1
- # Ignore build output
2
- .build
3
- dist
4
- coverage
5
-
6
- # Node
7
- node_modules
8
-
9
- # Dependency lockfiles (if you don't want Prettier to format them)
10
- package-lock.json
11
- yarn.lock
12
-
13
- # Serverless, system, IDE, or OS files
14
- .serverless
15
- .DS_Store
16
- .env
17
- *.log
18
-
19
- # Lambdas' bundles (if any)
20
- bundle-*.js
@@ -1,6 +0,0 @@
1
- {
2
- "semi": true,
3
- "singleQuote": false,
4
- "printWidth": 100,
5
- "trailingComma": "all"
6
- }
@@ -1,36 +0,0 @@
1
- # Serverless Typescript Boilerplate
2
-
3
- Minimalist project template to jump start a Serverless application in TypeScript.
4
-
5
- ## Keys features
6
-
7
- The current Serverless Starter Kit adds a light layer on top of the Serverless framework with modern JavaScript tools:
8
-
9
- - **TypeScript** Support.
10
- - **Offline** mode
11
- - Formatting with Prettier to enforce a consistent code style.
12
-
13
- ## Services
14
-
15
- Currently, the boilerplate is built and tested on AWS using the following services.
16
-
17
- - AWS Lambda
18
-
19
- ## Quick start
20
-
21
- ```
22
- npm install
23
- npm run offline
24
- ```
25
-
26
- ### Deployment
27
-
28
- Make sure you have AWS Cli configured
29
-
30
- ```bash
31
- npm run deploy
32
- ```
33
-
34
- ## Serverless plugins
35
-
36
- - [serverless-offline](https://github.com/dherault/serverless-offline): run your services offline for e.g. testing
@@ -1,36 +0,0 @@
1
- version: "3.8"
2
- services:
3
- dynamodb-local:
4
- image: amazon/dynamodb-local
5
- container_name: dynamodb-local
6
- restart: unless-stopped
7
- ports:
8
- - "8000:8000"
9
- command: "-jar DynamoDBLocal.jar -inMemory -sharedDb"
10
- working_dir: /home/dynamodblocal
11
- volumes:
12
- - dynamodb_data:/home/dynamodblocal/data
13
- environment:
14
- - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-dummy}
15
- - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-dummy}
16
- - AWS_REGION=${AWS_REGION:-eu-west-2}
17
- healthcheck:
18
- test: ["CMD", "curl", "-f", "http://localhost:8000/"]
19
- interval: 10s
20
- timeout: 5s
21
- retries: 5
22
-
23
- dynamodb-admin:
24
- image: aaronshaf/dynamodb-admin
25
- ports:
26
- - "8001:8001"
27
- environment:
28
- - DYNAMO_ENDPOINT=${DYNAMO_ENDPOINT:-http://dynamodb-local:8000}
29
- - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-dummy}
30
- - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-dummy}
31
- - AWS_REGION=${AWS_REGION:-eu-west-2}
32
- links:
33
- - dynamodb-local
34
-
35
- volumes:
36
- dynamodb_data: