create-npkg 0.0.1
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 +191 -0
- package/dist/src/index.d.ts +61 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +417 -0
- package/dist/src/index.js.map +1 -0
- package/package.json +42 -0
- package/src/index.ts +468 -0
- package/test/index.test.ts +879 -0
- package/tsconfig.json +31 -0
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-npkg",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Create a new TypeScript npm package with best practices",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-npkg": "./dist/src/index.js"
|
|
8
|
+
},
|
|
9
|
+
"exports": "./dist/index.js",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsc --watch",
|
|
13
|
+
"prepublishOnly": "npm run build",
|
|
14
|
+
"test": "node --test dist/test/**/*.test.js",
|
|
15
|
+
"test:watch": "node --test --watch dist/test/**/*.test.js"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"create",
|
|
19
|
+
"typescript",
|
|
20
|
+
"npm",
|
|
21
|
+
"package",
|
|
22
|
+
"scaffold",
|
|
23
|
+
"generator"
|
|
24
|
+
],
|
|
25
|
+
"author": "",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=18.0.0"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"prompts": "^2.4.2",
|
|
32
|
+
"kleur": "^4.1.5",
|
|
33
|
+
"ora": "^6.3.1",
|
|
34
|
+
"fs-extra": "^11.2.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^20.11.0",
|
|
38
|
+
"@types/fs-extra": "^11.0.4",
|
|
39
|
+
"@types/prompts": "^2.4.9",
|
|
40
|
+
"typescript": "^5.3.3"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* create-npkg - Create a new TypeScript npm package
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'fs-extra';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import prompts from 'prompts';
|
|
10
|
+
import kleur from 'kleur';
|
|
11
|
+
import ora from 'ora';
|
|
12
|
+
|
|
13
|
+
export interface Options {
|
|
14
|
+
pkgName: string;
|
|
15
|
+
description: string;
|
|
16
|
+
author: string;
|
|
17
|
+
license: string;
|
|
18
|
+
includeTests: boolean;
|
|
19
|
+
initGit: boolean;
|
|
20
|
+
installDeps: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface TemplateContext {
|
|
24
|
+
name: string;
|
|
25
|
+
description: string;
|
|
26
|
+
author: string;
|
|
27
|
+
license: string;
|
|
28
|
+
includeTests: boolean;
|
|
29
|
+
year: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Validate package name according to npm naming rules
|
|
34
|
+
*/
|
|
35
|
+
export function validatePackageName(name: string): { valid: boolean; error?: string } {
|
|
36
|
+
if (!name) {
|
|
37
|
+
return { valid: false, error: 'Package name is required' };
|
|
38
|
+
}
|
|
39
|
+
if (!/^[a-z0-9-]+$/.test(name)) {
|
|
40
|
+
return { valid: false, error: 'Package name must be lowercase, alphanumeric, and may contain hyphens' };
|
|
41
|
+
}
|
|
42
|
+
return { valid: true };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Main CLI entry point
|
|
47
|
+
*/
|
|
48
|
+
export async function main(): Promise<void> {
|
|
49
|
+
console.log('');
|
|
50
|
+
console.log(kleur.cyan('╔═══════════════════════════════════════╗'));
|
|
51
|
+
console.log(kleur.cyan('║') + kleur.bold(' create-npkg ') + kleur.cyan(' ║'));
|
|
52
|
+
console.log(kleur.cyan('║') + kleur.dim(' Create a TypeScript npm package ') + kleur.cyan('║'));
|
|
53
|
+
console.log(kleur.cyan('╚═══════════════════════════════════════╝'));
|
|
54
|
+
console.log('');
|
|
55
|
+
|
|
56
|
+
// Get package name from args or prompt
|
|
57
|
+
const args = process.argv.slice(2);
|
|
58
|
+
let pkgName = args[0];
|
|
59
|
+
|
|
60
|
+
// Check for flags
|
|
61
|
+
const noGitIndex = args.indexOf('--no-git');
|
|
62
|
+
const noInstallIndex = args.indexOf('--no-install');
|
|
63
|
+
const yesIndex = args.indexOf('--yes') !== -1 || args.indexOf('-y') !== -1;
|
|
64
|
+
|
|
65
|
+
const initGit = noGitIndex === -1;
|
|
66
|
+
const installDeps = noInstallIndex === -1;
|
|
67
|
+
const useDefaults = yesIndex;
|
|
68
|
+
|
|
69
|
+
// Remove flags from args if present
|
|
70
|
+
if (noGitIndex > 0) args.splice(noGitIndex, 1);
|
|
71
|
+
if (noInstallIndex > 0) args.splice(noInstallIndex, 1);
|
|
72
|
+
|
|
73
|
+
// Prompt for package name if not provided
|
|
74
|
+
if (!pkgName) {
|
|
75
|
+
const response = await prompts({
|
|
76
|
+
type: 'text',
|
|
77
|
+
name: 'pkgName',
|
|
78
|
+
message: 'What is your package name?',
|
|
79
|
+
initial: 'my-package',
|
|
80
|
+
validate: (value: string) => {
|
|
81
|
+
if (!value) return 'Package name is required';
|
|
82
|
+
if (!/^[a-z0-9-]+$/.test(value)) {
|
|
83
|
+
return 'Package name must be lowercase, alphanumeric, and may contain hyphens';
|
|
84
|
+
}
|
|
85
|
+
if (fs.existsSync(path.join(process.cwd(), value))) {
|
|
86
|
+
return `Directory "${value}" already exists`;
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
pkgName = response.pkgName;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!pkgName) {
|
|
95
|
+
console.error(kleur.red('✖ Package name is required'));
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Validate package name
|
|
100
|
+
if (!/^[a-z0-9-]+$/.test(pkgName)) {
|
|
101
|
+
console.error(kleur.red('✖ Package name must be lowercase, alphanumeric, and may contain hyphens'));
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Check if directory exists
|
|
106
|
+
const targetDir = path.join(process.cwd(), pkgName);
|
|
107
|
+
if (fs.existsSync(targetDir)) {
|
|
108
|
+
console.error(kleur.red(`✖ Directory "${pkgName}" already exists`));
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Prompt for additional options (or use defaults)
|
|
113
|
+
const responses = useDefaults ? {
|
|
114
|
+
description: 'A TypeScript package',
|
|
115
|
+
author: '',
|
|
116
|
+
license: 'MIT',
|
|
117
|
+
includeTests: false
|
|
118
|
+
} : await prompts(
|
|
119
|
+
[
|
|
120
|
+
{
|
|
121
|
+
type: 'text',
|
|
122
|
+
name: 'description',
|
|
123
|
+
message: 'Package description:',
|
|
124
|
+
initial: 'A TypeScript package'
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
type: 'text',
|
|
128
|
+
name: 'author',
|
|
129
|
+
message: 'Author:',
|
|
130
|
+
initial: ''
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
type: 'select',
|
|
134
|
+
name: 'license',
|
|
135
|
+
message: 'License:',
|
|
136
|
+
choices: [
|
|
137
|
+
{ title: 'MIT', value: 'MIT' },
|
|
138
|
+
{ title: 'Apache-2.0', value: 'Apache-2.0' },
|
|
139
|
+
{ title: 'ISC', value: 'ISC' },
|
|
140
|
+
{ title: 'BSD-3-Clause', value: 'BSD-3-Clause' }
|
|
141
|
+
],
|
|
142
|
+
initial: 0
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
type: 'confirm',
|
|
146
|
+
name: 'includeTests',
|
|
147
|
+
message: 'Include testing setup?',
|
|
148
|
+
initial: false
|
|
149
|
+
}
|
|
150
|
+
],
|
|
151
|
+
{
|
|
152
|
+
onCancel: () => {
|
|
153
|
+
console.log(kleur.yellow('✖ Operation cancelled'));
|
|
154
|
+
process.exit(0);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const options: Options = {
|
|
160
|
+
pkgName,
|
|
161
|
+
description: responses.description || 'A TypeScript package',
|
|
162
|
+
author: responses.author || '',
|
|
163
|
+
license: responses.license || 'MIT',
|
|
164
|
+
includeTests: responses.includeTests,
|
|
165
|
+
initGit,
|
|
166
|
+
installDeps
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
await createPackage(options);
|
|
171
|
+
} catch (error) {
|
|
172
|
+
if (error instanceof Error) {
|
|
173
|
+
console.error(kleur.red(`✖ ${error.message}`));
|
|
174
|
+
}
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Create the package structure
|
|
181
|
+
*/
|
|
182
|
+
export async function createPackage(options: Options, cwd: string = process.cwd()): Promise<void> {
|
|
183
|
+
const { pkgName } = options;
|
|
184
|
+
const targetDir = path.join(cwd, pkgName);
|
|
185
|
+
|
|
186
|
+
const spinner = ora('Creating package structure...').start();
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
// Create directory
|
|
190
|
+
await fs.ensureDir(targetDir);
|
|
191
|
+
await fs.ensureDir(path.join(targetDir, 'src'));
|
|
192
|
+
|
|
193
|
+
// Template context
|
|
194
|
+
const context: TemplateContext = {
|
|
195
|
+
name: pkgName,
|
|
196
|
+
description: options.description,
|
|
197
|
+
author: options.author,
|
|
198
|
+
license: options.license,
|
|
199
|
+
includeTests: options.includeTests,
|
|
200
|
+
year: new Date().getFullYear()
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// Write package.json
|
|
204
|
+
const pkgJson = generatePackageJson(context);
|
|
205
|
+
await fs.writeJSON(path.join(targetDir, 'package.json'), pkgJson, { spaces: 2 });
|
|
206
|
+
|
|
207
|
+
// Write tsconfig.json
|
|
208
|
+
const tsConfig = generateTsConfig(context);
|
|
209
|
+
await fs.writeJSON(path.join(targetDir, 'tsconfig.json'), tsConfig, { spaces: 2 });
|
|
210
|
+
|
|
211
|
+
// Write .gitignore
|
|
212
|
+
await fs.writeFile(path.join(targetDir, '.gitignore'), generateGitignore());
|
|
213
|
+
|
|
214
|
+
// Write README.md
|
|
215
|
+
await fs.writeFile(path.join(targetDir, 'README.md'), generateReadme(context));
|
|
216
|
+
|
|
217
|
+
// Write src/index.ts
|
|
218
|
+
await fs.writeFile(path.join(targetDir, 'src/index.ts'), generateIndexFile(context));
|
|
219
|
+
|
|
220
|
+
// Write test files if requested
|
|
221
|
+
if (options.includeTests) {
|
|
222
|
+
await fs.ensureDir(path.join(targetDir, 'tests'));
|
|
223
|
+
await fs.writeFile(path.join(targetDir, 'tests/index.test.ts'), generateTestFile(context));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
spinner.succeed('Package structure created');
|
|
227
|
+
|
|
228
|
+
// Initialize git if requested
|
|
229
|
+
if (options.initGit) {
|
|
230
|
+
spinner.start('Initializing git repository');
|
|
231
|
+
const { spawn } = await import('child_process');
|
|
232
|
+
await new Promise<void>((resolve, reject) => {
|
|
233
|
+
const git = spawn('git', ['init'], { cwd: targetDir });
|
|
234
|
+
git.on('close', (code) => {
|
|
235
|
+
if (code === 0) resolve();
|
|
236
|
+
else reject(new Error('Git initialization failed'));
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
spinner.succeed('Git repository initialized');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Install dependencies if requested
|
|
243
|
+
if (options.installDeps) {
|
|
244
|
+
spinner.start('Installing dependencies');
|
|
245
|
+
const { spawn } = await import('child_process');
|
|
246
|
+
await new Promise<void>((resolve, reject) => {
|
|
247
|
+
const npm = spawn('npm', ['install'], {
|
|
248
|
+
cwd: targetDir,
|
|
249
|
+
stdio: 'inherit'
|
|
250
|
+
});
|
|
251
|
+
npm.on('close', (code) => {
|
|
252
|
+
if (code === 0) resolve();
|
|
253
|
+
else reject(new Error('Dependency installation failed'));
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
spinner.succeed('Dependencies installed');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Print success message
|
|
260
|
+
console.log('');
|
|
261
|
+
console.log(kleur.green('✓ Package created successfully!'));
|
|
262
|
+
console.log('');
|
|
263
|
+
console.log(kleur.bold('Next steps:'));
|
|
264
|
+
console.log(` ${kleur.cyan(`cd ${pkgName}`)}`);
|
|
265
|
+
console.log(` ${kleur.cyan('npm run dev')}`);
|
|
266
|
+
console.log('');
|
|
267
|
+
|
|
268
|
+
} catch (error) {
|
|
269
|
+
spinner.fail('Failed to create package');
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Generate package.json
|
|
276
|
+
*/
|
|
277
|
+
export function generatePackageJson(context: TemplateContext): Record<string, any> {
|
|
278
|
+
const devDependencies: Record<string, string> = {
|
|
279
|
+
'@types/node': '^20.11.0',
|
|
280
|
+
'typescript': '^5.3.3'
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
if (context.includeTests) {
|
|
284
|
+
devDependencies['@types/node'] = '^20.11.0';
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
name: context.name,
|
|
289
|
+
version: '1.0.0',
|
|
290
|
+
type: 'module',
|
|
291
|
+
description: context.description,
|
|
292
|
+
main: './dist/index.js',
|
|
293
|
+
types: './dist/index.d.ts',
|
|
294
|
+
exports: {
|
|
295
|
+
'.': {
|
|
296
|
+
import: './dist/index.js',
|
|
297
|
+
types: './dist/index.d.ts'
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
files: ['dist'],
|
|
301
|
+
scripts: {
|
|
302
|
+
build: 'tsc',
|
|
303
|
+
dev: 'tsc --watch',
|
|
304
|
+
prepublishOnly: 'npm run build',
|
|
305
|
+
test: context.includeTests ? 'node --test' : undefined
|
|
306
|
+
},
|
|
307
|
+
keywords: [],
|
|
308
|
+
author: context.author,
|
|
309
|
+
license: context.license,
|
|
310
|
+
engines: {
|
|
311
|
+
node: '>=18.0.0'
|
|
312
|
+
},
|
|
313
|
+
devDependencies
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Generate tsconfig.json
|
|
319
|
+
*/
|
|
320
|
+
export function generateTsConfig(_context: TemplateContext): Record<string, any> {
|
|
321
|
+
return {
|
|
322
|
+
compilerOptions: {
|
|
323
|
+
target: 'ES2022',
|
|
324
|
+
module: 'NodeNext',
|
|
325
|
+
moduleResolution: 'NodeNext',
|
|
326
|
+
lib: ['ES2022'],
|
|
327
|
+
declaration: true,
|
|
328
|
+
declarationMap: true,
|
|
329
|
+
sourceMap: true,
|
|
330
|
+
outDir: './dist',
|
|
331
|
+
rootDir: './src',
|
|
332
|
+
strict: true,
|
|
333
|
+
noImplicitAny: true,
|
|
334
|
+
strictNullChecks: true,
|
|
335
|
+
strictFunctionTypes: true,
|
|
336
|
+
strictBindCallApply: true,
|
|
337
|
+
strictPropertyInitialization: true,
|
|
338
|
+
noImplicitThis: true,
|
|
339
|
+
alwaysStrict: true,
|
|
340
|
+
noUnusedLocals: true,
|
|
341
|
+
noUnusedParameters: true,
|
|
342
|
+
noImplicitReturns: true,
|
|
343
|
+
noFallthroughCasesInSwitch: true,
|
|
344
|
+
esModuleInterop: true,
|
|
345
|
+
skipLibCheck: true,
|
|
346
|
+
forceConsistentCasingInFileNames: true,
|
|
347
|
+
resolveJsonModule: true,
|
|
348
|
+
paths: {
|
|
349
|
+
'@src/*': ['./src/*']
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
include: ['src/**/*'],
|
|
353
|
+
exclude: ['node_modules', 'dist', '**/*.test.ts']
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Generate .gitignore
|
|
359
|
+
*/
|
|
360
|
+
export function generateGitignore(): string {
|
|
361
|
+
return `# Dependencies
|
|
362
|
+
node_modules/
|
|
363
|
+
|
|
364
|
+
# Build output
|
|
365
|
+
dist/
|
|
366
|
+
|
|
367
|
+
# IDE
|
|
368
|
+
.idea/
|
|
369
|
+
.vscode/
|
|
370
|
+
*.swp
|
|
371
|
+
*.swo
|
|
372
|
+
|
|
373
|
+
# OS
|
|
374
|
+
.DS_Store
|
|
375
|
+
Thumbs.db
|
|
376
|
+
|
|
377
|
+
# Logs
|
|
378
|
+
*.log
|
|
379
|
+
npm-debug.log*
|
|
380
|
+
yarn-debug.log*
|
|
381
|
+
yarn-error.log*
|
|
382
|
+
|
|
383
|
+
# Testing
|
|
384
|
+
coverage/
|
|
385
|
+
.nyc_output/
|
|
386
|
+
|
|
387
|
+
# Environment
|
|
388
|
+
.env
|
|
389
|
+
.env.local
|
|
390
|
+
`;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Generate README.md
|
|
395
|
+
*/
|
|
396
|
+
export function generateReadme(context: TemplateContext): string {
|
|
397
|
+
return `# ${context.name}
|
|
398
|
+
|
|
399
|
+
${context.description}
|
|
400
|
+
|
|
401
|
+
## Installation
|
|
402
|
+
|
|
403
|
+
\`\`\`bash
|
|
404
|
+
npm install ${context.name}
|
|
405
|
+
\`\`\`
|
|
406
|
+
|
|
407
|
+
## Usage
|
|
408
|
+
|
|
409
|
+
\`\`\`typescript
|
|
410
|
+
import { something } from '${context.name}';
|
|
411
|
+
|
|
412
|
+
// Your code here
|
|
413
|
+
\`\`\`
|
|
414
|
+
|
|
415
|
+
## Development
|
|
416
|
+
|
|
417
|
+
\`\`\`bash
|
|
418
|
+
# Install dependencies
|
|
419
|
+
npm install
|
|
420
|
+
|
|
421
|
+
# Build
|
|
422
|
+
npm run build
|
|
423
|
+
|
|
424
|
+
# Watch mode
|
|
425
|
+
npm run dev
|
|
426
|
+
\`\`\`
|
|
427
|
+
|
|
428
|
+
## License
|
|
429
|
+
|
|
430
|
+
${context.license} © ${context.year} ${context.author ? context.author : 'Contributors'}
|
|
431
|
+
`;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Generate src/index.ts
|
|
436
|
+
*/
|
|
437
|
+
export function generateIndexFile(_context: TemplateContext): string {
|
|
438
|
+
return `/**
|
|
439
|
+
* Main entry point for ${_context.name}
|
|
440
|
+
*/
|
|
441
|
+
|
|
442
|
+
export function hello(name: string): string {
|
|
443
|
+
return \`Hello, \${name}!\`;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export default hello;
|
|
447
|
+
`;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Generate test file
|
|
452
|
+
*/
|
|
453
|
+
export function generateTestFile(_context: TemplateContext): string {
|
|
454
|
+
return `import { test } from 'node:test';
|
|
455
|
+
import assert from 'node:assert';
|
|
456
|
+
import { hello } from '../src/index.js';
|
|
457
|
+
|
|
458
|
+
test('hello function', () => {
|
|
459
|
+
const result = hello('World');
|
|
460
|
+
assert.strictEqual(result, 'Hello, World!');
|
|
461
|
+
});
|
|
462
|
+
`;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Run if called directly
|
|
466
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
467
|
+
main();
|
|
468
|
+
}
|