create-npkg 0.0.1 → 0.0.3

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.
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { main } from '../dist/src/index.js';
3
+ main();
package/dist/src/index.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,18 +1,22 @@
1
1
  {
2
2
  "name": "create-npkg",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Create a new TypeScript npm package with best practices",
5
5
  "type": "module",
6
6
  "bin": {
7
- "create-npkg": "./dist/src/index.js"
7
+ "create-npkg": "./bin/create-npkg.js"
8
8
  },
9
9
  "exports": "./dist/index.js",
10
+ "files": [
11
+ "dist",
12
+ "bin",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
10
16
  "scripts": {
11
17
  "build": "tsc",
12
18
  "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"
19
+ "prepublishOnly": "npm run build"
16
20
  },
17
21
  "keywords": [
18
22
  "create",
package/src/index.ts DELETED
@@ -1,468 +0,0 @@
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
- }
@@ -1,879 +0,0 @@
1
- /**
2
- * Tests for create-npkg
3
- */
4
-
5
- import { test } from 'node:test';
6
- import assert from 'node:assert';
7
- import fs from 'fs-extra';
8
- import path from 'path';
9
- import { tmpdir } from 'os';
10
-
11
- import {
12
- validatePackageName,
13
- generatePackageJson,
14
- generateTsConfig,
15
- generateGitignore,
16
- generateReadme,
17
- generateIndexFile,
18
- generateTestFile,
19
- createPackage,
20
- type Options,
21
- type TemplateContext
22
- } from '../src/index.js';
23
-
24
- // Helper to create a temporary directory
25
- async function createTempDir(): Promise<string> {
26
- const tempDir = path.join(tmpdir(), `create-npkg-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
27
- await fs.ensureDir(tempDir);
28
- return tempDir;
29
- }
30
-
31
- // Helper to clean up a directory
32
- async function cleanupDir(dir: string): Promise<void> {
33
- await fs.remove(dir);
34
- }
35
-
36
- test('validatePackageName - valid names', () => {
37
- const validNames = [
38
- 'my-package',
39
- 'mypackage',
40
- 'my-package-123',
41
- 'package',
42
- 'a',
43
- 'test123'
44
- ];
45
-
46
- for (const name of validNames) {
47
- const result = validatePackageName(name);
48
- assert.strictEqual(result.valid, true, `Expected "${name}" to be valid`);
49
- assert.strictEqual(result.error, undefined);
50
- }
51
- });
52
-
53
- test('validatePackageName - invalid names', () => {
54
- const invalidNames = [
55
- { name: '', expectedError: 'Package name is required' },
56
- { name: 'MyPackage', expectedError: 'lowercase' },
57
- { name: 'my_package', expectedError: 'lowercase' },
58
- { name: 'my package', expectedError: 'lowercase' },
59
- { name: 'my@package', expectedError: 'lowercase' },
60
- { name: '../malicious', expectedError: 'lowercase' }
61
- ];
62
-
63
- for (const { name, expectedError } of invalidNames) {
64
- const result = validatePackageName(name);
65
- assert.strictEqual(result.valid, false, `Expected "${name}" to be invalid`);
66
- assert.ok(result.error?.includes(expectedError), `Expected error to contain "${expectedError}", got "${result.error}"`);
67
- }
68
- });
69
-
70
- test('generatePackageJson - basic structure', () => {
71
- const context: TemplateContext = {
72
- name: 'test-package',
73
- description: 'A test package',
74
- author: 'Test Author',
75
- license: 'MIT',
76
- includeTests: false,
77
- year: 2024
78
- };
79
-
80
- const pkgJson = generatePackageJson(context);
81
-
82
- assert.strictEqual(pkgJson.name, 'test-package');
83
- assert.strictEqual(pkgJson.version, '1.0.0');
84
- assert.strictEqual(pkgJson.description, 'A test package');
85
- assert.strictEqual(pkgJson.author, 'Test Author');
86
- assert.strictEqual(pkgJson.license, 'MIT');
87
- assert.strictEqual(pkgJson.main, './dist/index.js');
88
- assert.strictEqual(pkgJson.types, './dist/index.d.ts');
89
- assert.deepStrictEqual(pkgJson.files, ['dist']);
90
- assert.ok(pkgJson.scripts.build);
91
- assert.ok(pkgJson.scripts.dev);
92
- assert.ok(pkgJson.scripts.prepublishOnly);
93
- assert.strictEqual(pkgJson.engines.node, '>=18.0.0');
94
- assert.ok(pkgJson.devDependencies);
95
- assert.ok(pkgJson.devDependencies.typescript);
96
- });
97
-
98
- test('generatePackageJson - with tests', () => {
99
- const context: TemplateContext = {
100
- name: 'test-package',
101
- description: 'A test package',
102
- author: 'Test Author',
103
- license: 'MIT',
104
- includeTests: true,
105
- year: 2024
106
- };
107
-
108
- const pkgJson = generatePackageJson(context);
109
-
110
- assert.ok(pkgJson.scripts.test);
111
- assert.strictEqual(pkgJson.scripts.test, 'node --test');
112
- });
113
-
114
- test('generatePackageJson - exports field', () => {
115
- const context: TemplateContext = {
116
- name: 'test-package',
117
- description: 'A test package',
118
- author: 'Test Author',
119
- license: 'MIT',
120
- includeTests: false,
121
- year: 2024
122
- };
123
-
124
- const pkgJson = generatePackageJson(context);
125
-
126
- assert.ok(pkgJson.exports);
127
- assert.ok(pkgJson.exports['.']);
128
- assert.ok(pkgJson.exports['.'].import);
129
- assert.ok(pkgJson.exports['.'].types);
130
- assert.strictEqual(pkgJson.exports['.'].import, './dist/index.js');
131
- assert.strictEqual(pkgJson.exports['.'].types, './dist/index.d.ts');
132
- });
133
-
134
- test('generateTsConfig - strict configuration', () => {
135
- const context: TemplateContext = {
136
- name: 'test-package',
137
- description: 'A test package',
138
- author: 'Test Author',
139
- license: 'MIT',
140
- includeTests: false,
141
- year: 2024
142
- };
143
-
144
- const tsConfig = generateTsConfig(context);
145
-
146
- assert.ok(tsConfig.compilerOptions);
147
- assert.strictEqual(tsConfig.compilerOptions.target, 'ES2022');
148
- assert.strictEqual(tsConfig.compilerOptions.module, 'NodeNext');
149
- assert.strictEqual(tsConfig.compilerOptions.moduleResolution, 'NodeNext');
150
- assert.strictEqual(tsConfig.compilerOptions.outDir, './dist');
151
- assert.strictEqual(tsConfig.compilerOptions.rootDir, './src');
152
- assert.strictEqual(tsConfig.compilerOptions.strict, true);
153
- assert.strictEqual(tsConfig.compilerOptions.noImplicitAny, true);
154
- assert.strictEqual(tsConfig.compilerOptions.strictNullChecks, true);
155
- assert.strictEqual(tsConfig.compilerOptions.declaration, true);
156
- assert.strictEqual(tsConfig.compilerOptions.sourceMap, true);
157
- assert.deepStrictEqual(tsConfig.include, ['src/**/*']);
158
- assert.deepStrictEqual(tsConfig.exclude, ['node_modules', 'dist', '**/*.test.ts']);
159
- });
160
-
161
- test('generateTsConfig - path aliases', () => {
162
- const context: TemplateContext = {
163
- name: 'test-package',
164
- description: 'A test package',
165
- author: 'Test Author',
166
- license: 'MIT',
167
- includeTests: false,
168
- year: 2024
169
- };
170
-
171
- const tsConfig = generateTsConfig(context);
172
-
173
- assert.ok(tsConfig.compilerOptions.paths);
174
- assert.ok(tsConfig.compilerOptions.paths['@src/*']);
175
- assert.deepStrictEqual(tsConfig.compilerOptions.paths['@src/*'], ['./src/*']);
176
- });
177
-
178
- test('generateGitignore - contains essential patterns', () => {
179
- const gitignore = generateGitignore();
180
-
181
- assert.ok(gitignore.includes('node_modules/'));
182
- assert.ok(gitignore.includes('dist/'));
183
- assert.ok(gitignore.includes('.DS_Store'));
184
- assert.ok(gitignore.includes('*.log'));
185
- assert.ok(gitignore.includes('.env'));
186
- assert.ok(gitignore.includes('.vscode/'));
187
- assert.ok(gitignore.includes('.idea/'));
188
- });
189
-
190
- test('generateReadme - contains project information', () => {
191
- const context: TemplateContext = {
192
- name: 'test-package',
193
- description: 'A test package',
194
- author: 'Test Author',
195
- license: 'MIT',
196
- includeTests: false,
197
- year: 2024
198
- };
199
-
200
- const readme = generateReadme(context);
201
-
202
- assert.ok(readme.includes('# test-package'));
203
- assert.ok(readme.includes('A test package'));
204
- assert.ok(readme.includes('npm install test-package'));
205
- assert.ok(readme.includes('MIT © 2024 Test Author'));
206
- assert.ok(readme.includes('npm run build'));
207
- assert.ok(readme.includes('npm run dev'));
208
- });
209
-
210
- test('generateReadme - without author', () => {
211
- const context: TemplateContext = {
212
- name: 'test-package',
213
- description: 'A test package',
214
- author: '',
215
- license: 'MIT',
216
- includeTests: false,
217
- year: 2024
218
- };
219
-
220
- const readme = generateReadme(context);
221
-
222
- assert.ok(readme.includes('MIT © 2024 Contributors'));
223
- });
224
-
225
- test('generateIndexFile - contains valid TypeScript', () => {
226
- const context: TemplateContext = {
227
- name: 'test-package',
228
- description: 'A test package',
229
- author: 'Test Author',
230
- license: 'MIT',
231
- includeTests: false,
232
- year: 2024
233
- };
234
-
235
- const indexFile = generateIndexFile(context);
236
-
237
- assert.ok(indexFile.includes('export function hello'));
238
- assert.ok(indexFile.includes('Main entry point for test-package'));
239
- assert.ok(indexFile.includes('Hello, ${name}!'));
240
- assert.ok(indexFile.includes('export default hello'));
241
- });
242
-
243
- test('generateTestFile - contains valid test code', () => {
244
- const context: TemplateContext = {
245
- name: 'test-package',
246
- description: 'A test package',
247
- author: 'Test Author',
248
- license: 'MIT',
249
- includeTests: false,
250
- year: 2024
251
- };
252
-
253
- const testFile = generateTestFile(context);
254
-
255
- assert.ok(testFile.includes("import { test } from 'node:test'"));
256
- assert.ok(testFile.includes("import assert from 'node:assert'"));
257
- assert.ok(testFile.includes("import { hello } from '../src/index.js'"));
258
- assert.ok(testFile.includes("test('hello function'"));
259
- assert.ok(testFile.includes("assert.strictEqual(result, 'Hello, World!')"));
260
- });
261
-
262
- test('createPackage - creates basic package structure', async () => {
263
- const tempDir = await createTempDir();
264
-
265
- try {
266
- const options: Options = {
267
- pkgName: 'test-pkg',
268
- description: 'A test package',
269
- author: 'Test Author',
270
- license: 'MIT',
271
- includeTests: false,
272
- initGit: false,
273
- installDeps: false
274
- };
275
-
276
- await createPackage(options, tempDir);
277
-
278
- const pkgDir = path.join(tempDir, 'test-pkg');
279
-
280
- // Check directory exists
281
- assert.ok(await fs.pathExists(pkgDir));
282
-
283
- // Check files exist
284
- assert.ok(await fs.pathExists(path.join(pkgDir, 'package.json')));
285
- assert.ok(await fs.pathExists(path.join(pkgDir, 'tsconfig.json')));
286
- assert.ok(await fs.pathExists(path.join(pkgDir, '.gitignore')));
287
- assert.ok(await fs.pathExists(path.join(pkgDir, 'README.md')));
288
- assert.ok(await fs.pathExists(path.join(pkgDir, 'src', 'index.ts')));
289
-
290
- // Check src directory exists
291
- assert.ok(await fs.pathExists(path.join(pkgDir, 'src')));
292
-
293
- // Check package.json content
294
- const pkgJson = await fs.readJSON(path.join(pkgDir, 'package.json'));
295
- assert.strictEqual(pkgJson.name, 'test-pkg');
296
- assert.strictEqual(pkgJson.description, 'A test package');
297
- assert.strictEqual(pkgJson.author, 'Test Author');
298
- assert.strictEqual(pkgJson.license, 'MIT');
299
-
300
- } finally {
301
- await cleanupDir(tempDir);
302
- }
303
- });
304
-
305
- test('createPackage - creates package with tests', async () => {
306
- const tempDir = await createTempDir();
307
-
308
- try {
309
- const options: Options = {
310
- pkgName: 'test-pkg-with-tests',
311
- description: 'A test package',
312
- author: 'Test Author',
313
- license: 'MIT',
314
- includeTests: true,
315
- initGit: false,
316
- installDeps: false
317
- };
318
-
319
- await createPackage(options, tempDir);
320
-
321
- const pkgDir = path.join(tempDir, 'test-pkg-with-tests');
322
-
323
- // Check test directory and file exist
324
- assert.ok(await fs.pathExists(path.join(pkgDir, 'tests')));
325
- assert.ok(await fs.pathExists(path.join(pkgDir, 'tests', 'index.test.ts')));
326
-
327
- // Check package.json has test script
328
- const pkgJson = await fs.readJSON(path.join(pkgDir, 'package.json'));
329
- assert.ok(pkgJson.scripts.test);
330
-
331
- } finally {
332
- await cleanupDir(tempDir);
333
- }
334
- });
335
-
336
- test('createPackage - creates package with different license', async () => {
337
- const tempDir = await createTempDir();
338
-
339
- try {
340
- const options: Options = {
341
- pkgName: 'test-pkg-apache',
342
- description: 'A test package',
343
- author: 'Test Author',
344
- license: 'Apache-2.0',
345
- includeTests: false,
346
- initGit: false,
347
- installDeps: false
348
- };
349
-
350
- await createPackage(options, tempDir);
351
-
352
- const pkgDir = path.join(tempDir, 'test-pkg-apache');
353
-
354
- // Check package.json license
355
- const pkgJson = await fs.readJSON(path.join(pkgDir, 'package.json'));
356
- assert.strictEqual(pkgJson.license, 'Apache-2.0');
357
-
358
- // Check README license
359
- const readme = await fs.readFile(path.join(pkgDir, 'README.md'), 'utf-8');
360
- assert.ok(readme.includes('Apache-2.0'));
361
-
362
- } finally {
363
- await cleanupDir(tempDir);
364
- }
365
- });
366
-
367
- test('createPackage - generates valid TypeScript files', async () => {
368
- const tempDir = await createTempDir();
369
-
370
- try {
371
- const options: Options = {
372
- pkgName: 'test-pkg-valid-ts',
373
- description: 'A test package',
374
- author: 'Test Author',
375
- license: 'MIT',
376
- includeTests: false,
377
- initGit: false,
378
- installDeps: false
379
- };
380
-
381
- await createPackage(options, tempDir);
382
-
383
- const pkgDir = path.join(tempDir, 'test-pkg-valid-ts');
384
-
385
- // Read generated files
386
- const indexContent = await fs.readFile(path.join(pkgDir, 'src', 'index.ts'), 'utf-8');
387
- const tsConfig = await fs.readJSON(path.join(pkgDir, 'tsconfig.json'));
388
-
389
- // Verify index.ts is valid TypeScript
390
- assert.ok(indexContent.includes('export function hello'));
391
- assert.ok(indexContent.includes(': string'));
392
-
393
- // Verify tsconfig has proper settings
394
- assert.strictEqual(tsConfig.compilerOptions.strict, true);
395
- assert.strictEqual(tsConfig.compilerOptions.target, 'ES2022');
396
-
397
- } finally {
398
- await cleanupDir(tempDir);
399
- }
400
- });
401
-
402
- test('createPackage - handles different author names', async () => {
403
- const tempDir = await createTempDir();
404
-
405
- try {
406
- const options: Options = {
407
- pkgName: 'test-pkg-author',
408
- description: 'A test package',
409
- author: 'John Doe <john@example.com>',
410
- license: 'MIT',
411
- includeTests: false,
412
- initGit: false,
413
- installDeps: false
414
- };
415
-
416
- await createPackage(options, tempDir);
417
-
418
- const pkgDir = path.join(tempDir, 'test-pkg-author');
419
-
420
- const pkgJson = await fs.readJSON(path.join(pkgDir, 'package.json'));
421
- const readme = await fs.readFile(path.join(pkgDir, 'README.md'), 'utf-8');
422
-
423
- assert.strictEqual(pkgJson.author, 'John Doe <john@example.com>');
424
- assert.ok(readme.includes('John Doe <john@example.com>'));
425
-
426
- } finally {
427
- await cleanupDir(tempDir);
428
- }
429
- });
430
-
431
- test('createPackage - generates proper directory structure', async () => {
432
- const tempDir = await createTempDir();
433
-
434
- try {
435
- const options: Options = {
436
- pkgName: 'test-pkg-structure',
437
- description: 'A test package',
438
- author: 'Test Author',
439
- license: 'MIT',
440
- includeTests: false,
441
- initGit: false,
442
- installDeps: false
443
- };
444
-
445
- await createPackage(options, tempDir);
446
-
447
- const pkgDir = path.join(tempDir, 'test-pkg-structure');
448
-
449
- // List all files in directory
450
- const files = await fs.readdir(pkgDir);
451
- const srcFiles = await fs.readdir(path.join(pkgDir, 'src'));
452
-
453
- // Verify expected files
454
- assert.ok(files.includes('package.json'));
455
- assert.ok(files.includes('tsconfig.json'));
456
- assert.ok(files.includes('.gitignore'));
457
- assert.ok(files.includes('README.md'));
458
- assert.ok(files.includes('src'));
459
-
460
- // Verify src contents
461
- assert.ok(srcFiles.includes('index.ts'));
462
-
463
- } finally {
464
- await cleanupDir(tempDir);
465
- }
466
- });
467
-
468
- test('generatePackageJson - handles empty author', () => {
469
- const context: TemplateContext = {
470
- name: 'test-package',
471
- description: 'A test package',
472
- author: '',
473
- license: 'MIT',
474
- includeTests: false,
475
- year: 2024
476
- };
477
-
478
- const pkgJson = generatePackageJson(context);
479
-
480
- assert.strictEqual(pkgJson.author, '');
481
- });
482
-
483
- test('generatePackageJson - handles long descriptions', () => {
484
- const longDesc = 'This is a very long package description that spans multiple lines and contains a lot of information about what the package does and how it can be used by developers in their projects.';
485
-
486
- const context: TemplateContext = {
487
- name: 'test-package',
488
- description: longDesc,
489
- author: 'Test Author',
490
- license: 'MIT',
491
- includeTests: false,
492
- year: 2024
493
- };
494
-
495
- const pkgJson = generatePackageJson(context);
496
-
497
- assert.strictEqual(pkgJson.description, longDesc);
498
- });
499
-
500
- test('generateTsConfig - includes all necessary compiler options', () => {
501
- const context: TemplateContext = {
502
- name: 'test-package',
503
- description: 'A test package',
504
- author: 'Test Author',
505
- license: 'MIT',
506
- includeTests: false,
507
- year: 2024
508
- };
509
-
510
- const tsConfig = generateTsConfig(context);
511
- const opts = tsConfig.compilerOptions;
512
-
513
- // Check for important strict mode options
514
- assert.strictEqual(opts.strict, true);
515
- assert.strictEqual(opts.noImplicitAny, true);
516
- assert.strictEqual(opts.strictNullChecks, true);
517
- assert.strictEqual(opts.strictFunctionTypes, true);
518
- assert.strictEqual(opts.strictBindCallApply, true);
519
- assert.strictEqual(opts.strictPropertyInitialization, true);
520
- assert.strictEqual(opts.noImplicitThis, true);
521
-
522
- // Check for modern JS target
523
- assert.strictEqual(opts.target, 'ES2022');
524
-
525
- // Check for module resolution
526
- assert.strictEqual(opts.module, 'NodeNext');
527
- assert.strictEqual(opts.moduleResolution, 'NodeNext');
528
-
529
- // Check for declaration files
530
- assert.strictEqual(opts.declaration, true);
531
- assert.strictEqual(opts.declarationMap, true);
532
- assert.strictEqual(opts.sourceMap, true);
533
-
534
- // Check for output directory
535
- assert.strictEqual(opts.outDir, './dist');
536
- assert.strictEqual(opts.rootDir, './src');
537
- });
538
-
539
- // Boundary case tests
540
- test('validatePackageName - boundary cases', () => {
541
- const boundaryCases = [
542
- { name: '-', valid: true }, // Single hyphen is technically valid by regex
543
- { name: 'package-', valid: true },
544
- { name: '-package', valid: true },
545
- { name: 'my--package', valid: true },
546
- { name: 'a', valid: true },
547
- { name: '1', valid: true },
548
- { name: '1package', valid: true },
549
- { name: 'package-1-2-3', valid: true }
550
- ];
551
-
552
- for (const { name, valid } of boundaryCases) {
553
- const result = validatePackageName(name);
554
- assert.strictEqual(result.valid, valid, `Expected "${name}" to be ${valid ? 'valid' : 'invalid'}`);
555
- }
556
- });
557
-
558
- test('validatePackageName - npm reserved keywords', () => {
559
- const reservedNames = [
560
- 'node',
561
- 'npm',
562
- 'fs',
563
- 'path',
564
- 'http',
565
- 'https',
566
- 'stream',
567
- 'events',
568
- 'util',
569
- 'os'
570
- ];
571
-
572
- // Note: Current implementation allows these, but tests document current behavior
573
- for (const name of reservedNames) {
574
- const result = validatePackageName(name);
575
- assert.strictEqual(result.valid, true, `Currently allows reserved name "${name}"`);
576
- }
577
- });
578
-
579
- test('validatePackageName - special characters', () => {
580
- const specialCases = [
581
- { name: 'my.package', valid: false },
582
- { name: 'my_package', valid: false },
583
- { name: 'my@package', valid: false },
584
- { name: 'my+package', valid: false },
585
- { name: 'my~package', valid: false },
586
- { name: 'my package', valid: false },
587
- { name: 'my\npackage', valid: false },
588
- { name: 'my\tpackage', valid: false }
589
- ];
590
-
591
- for (const { name, valid } of specialCases) {
592
- const result = validatePackageName(name);
593
- assert.strictEqual(result.valid, valid, `Expected "${name}" to be ${valid ? 'valid' : 'invalid'}`);
594
- }
595
- });
596
-
597
- test('generatePackageJson - includes type module', () => {
598
- const context: TemplateContext = {
599
- name: 'test-package',
600
- description: 'A test package',
601
- author: 'Test Author',
602
- license: 'MIT',
603
- includeTests: false,
604
- year: 2024
605
- };
606
-
607
- const pkgJson = generatePackageJson(context);
608
- assert.strictEqual(pkgJson.type, 'module');
609
- });
610
-
611
- test('generatePackageJson - handles special characters in author', () => {
612
- const specialAuthors = [
613
- 'John Doe <john@example.com>',
614
- 'Jane Doe <jane@example.com> (https://jane.dev)',
615
- 'Åsa ÄäÖö',
616
- 'Developer "The Dev" Smith'
617
- ];
618
-
619
- for (const author of specialAuthors) {
620
- const context: TemplateContext = {
621
- name: 'test-package',
622
- description: 'A test package',
623
- author,
624
- license: 'MIT',
625
- includeTests: false,
626
- year: 2024
627
- };
628
-
629
- const pkgJson = generatePackageJson(context);
630
- assert.strictEqual(pkgJson.author, author);
631
- }
632
- });
633
-
634
- test('generatePackageJson - handles very long description', () => {
635
- const longDesc = 'A'.repeat(10000);
636
-
637
- const context: TemplateContext = {
638
- name: 'test-package',
639
- description: longDesc,
640
- author: 'Test Author',
641
- license: 'MIT',
642
- includeTests: false,
643
- year: 2024
644
- };
645
-
646
- const pkgJson = generatePackageJson(context);
647
- assert.strictEqual(pkgJson.description.length, 10000);
648
- });
649
-
650
- test('generateReadme - handles special characters in description', () => {
651
- const context: TemplateContext = {
652
- name: 'test-package',
653
- description: 'A package with <special> & "characters" and \'quotes\'',
654
- author: 'Test Author',
655
- license: 'MIT',
656
- includeTests: false,
657
- year: 2024
658
- };
659
-
660
- const readme = generateReadme(context);
661
- assert.ok(readme.includes('A package with <special> & "characters" and \'quotes\''));
662
- });
663
-
664
- test('createPackage - handles errors gracefully', async () => {
665
- const tempDir = await createTempDir();
666
-
667
- try {
668
- // Create a file with the same name as the target package
669
- const options: Options = {
670
- pkgName: 'test-pkg',
671
- description: 'A test package',
672
- author: 'Test Author',
673
- license: 'MIT',
674
- includeTests: false,
675
- initGit: false,
676
- installDeps: false
677
- };
678
-
679
- // Create a file instead of directory
680
- const filePath = path.join(tempDir, 'test-pkg');
681
- await fs.writeFile(filePath, 'This is a file, not a directory');
682
-
683
- // Should fail because test-pkg exists as a file
684
- await assert.rejects(
685
- async () => await createPackage(options, tempDir),
686
- /EEXIST|already exists|file already exists/
687
- );
688
-
689
- } finally {
690
- await cleanupDir(tempDir);
691
- }
692
- });
693
-
694
- test('createPackage - with git initialization', async () => {
695
- const tempDir = await createTempDir();
696
-
697
- try {
698
- const options: Options = {
699
- pkgName: 'test-pkg-git',
700
- description: 'A test package',
701
- author: 'Test Author',
702
- license: 'MIT',
703
- includeTests: false,
704
- initGit: true,
705
- installDeps: false
706
- };
707
-
708
- await createPackage(options, tempDir);
709
-
710
- const pkgDir = path.join(tempDir, 'test-pkg-git');
711
- const gitDir = path.join(pkgDir, '.git');
712
-
713
- // Check .git directory was created
714
- assert.ok(await fs.pathExists(gitDir));
715
-
716
- } finally {
717
- await cleanupDir(tempDir);
718
- }
719
- });
720
-
721
- test('createPackage - creates proper test directory structure', async () => {
722
- const tempDir = await createTempDir();
723
-
724
- try {
725
- const options: Options = {
726
- pkgName: 'test-pkg-tests',
727
- description: 'A test package',
728
- author: 'Test Author',
729
- license: 'MIT',
730
- includeTests: true,
731
- initGit: false,
732
- installDeps: false
733
- };
734
-
735
- await createPackage(options, tempDir);
736
-
737
- const pkgDir = path.join(tempDir, 'test-pkg-tests');
738
-
739
- // Check tests directory exists
740
- assert.ok(await fs.pathExists(path.join(pkgDir, 'tests')));
741
-
742
- // Check test file exists and has correct content
743
- const testContent = await fs.readFile(path.join(pkgDir, 'tests', 'index.test.ts'), 'utf-8');
744
- assert.ok(testContent.includes("import { test } from 'node:test'"));
745
- assert.ok(testContent.includes("import assert from 'node:assert'"));
746
- assert.ok(testContent.includes('hello'));
747
-
748
- } finally {
749
- await cleanupDir(tempDir);
750
- }
751
- });
752
-
753
- test('generateTsConfig - excludes test files', () => {
754
- const context: TemplateContext = {
755
- name: 'test-package',
756
- description: 'A test package',
757
- author: 'Test Author',
758
- license: 'MIT',
759
- includeTests: false,
760
- year: 2024
761
- };
762
-
763
- const tsConfig = generateTsConfig(context);
764
- assert.ok(tsConfig.exclude.includes('**/*.test.ts'));
765
- });
766
-
767
- test('generateTsConfig - includes all strict options', () => {
768
- const context: TemplateContext = {
769
- name: 'test-package',
770
- description: 'A test package',
771
- author: 'Test Author',
772
- license: 'MIT',
773
- includeTests: false,
774
- year: 2024
775
- };
776
-
777
- const tsConfig = generateTsConfig(context);
778
- const opts = tsConfig.compilerOptions;
779
-
780
- // Verify esModuleInterop is present (was an indentation bug)
781
- assert.strictEqual(opts.esModuleInterop, true);
782
-
783
- // Verify other important options
784
- assert.strictEqual(opts.alwaysStrict, true);
785
- assert.strictEqual(opts.noUnusedLocals, true);
786
- assert.strictEqual(opts.noUnusedParameters, true);
787
- assert.strictEqual(opts.noImplicitReturns, true);
788
- assert.strictEqual(opts.noFallthroughCasesInSwitch, true);
789
- });
790
-
791
- test('createPackage - all licenses generate correctly', async () => {
792
- const licenses = ['MIT', 'Apache-2.0', 'ISC', 'BSD-3-Clause'];
793
- const tempDir = await createTempDir();
794
-
795
- try {
796
- for (const license of licenses) {
797
- const options: Options = {
798
- pkgName: `test-pkg-${license.toLowerCase().replace('.', '-')}`,
799
- description: 'A test package',
800
- author: 'Test Author',
801
- license,
802
- includeTests: false,
803
- initGit: false,
804
- installDeps: false
805
- };
806
-
807
- await createPackage(options, tempDir);
808
-
809
- const pkgDir = path.join(tempDir, options.pkgName);
810
- const pkgJson = await fs.readJSON(path.join(pkgDir, 'package.json'));
811
-
812
- assert.strictEqual(pkgJson.license, license);
813
- }
814
- } finally {
815
- await cleanupDir(tempDir);
816
- }
817
- });
818
-
819
- test('createPackage - handles concurrent package creation', async () => {
820
- const tempDir = await createTempDir();
821
-
822
- try {
823
- const options1: Options = {
824
- pkgName: 'test-pkg-1',
825
- description: 'A test package',
826
- author: 'Test Author',
827
- license: 'MIT',
828
- includeTests: false,
829
- initGit: false,
830
- installDeps: false
831
- };
832
-
833
- const options2: Options = {
834
- pkgName: 'test-pkg-2',
835
- description: 'A test package',
836
- author: 'Test Author',
837
- license: 'MIT',
838
- includeTests: false,
839
- initGit: false,
840
- installDeps: false
841
- };
842
-
843
- // Create packages concurrently
844
- await Promise.all([
845
- createPackage(options1, tempDir),
846
- createPackage(options2, tempDir)
847
- ]);
848
-
849
- // Verify both packages exist
850
- assert.ok(await fs.pathExists(path.join(tempDir, 'test-pkg-1')));
851
- assert.ok(await fs.pathExists(path.join(tempDir, 'test-pkg-2')));
852
-
853
- } finally {
854
- await cleanupDir(tempDir);
855
- }
856
- });
857
-
858
- test('generateGitignore - covers all essential patterns', () => {
859
- const gitignore = generateGitignore();
860
- const requiredPatterns = [
861
- 'node_modules/',
862
- 'dist/',
863
- '.DS_Store',
864
- 'Thumbs.db',
865
- '*.log',
866
- '.env',
867
- '.vscode/',
868
- '.idea/',
869
- '*.swp',
870
- '*.swo',
871
- 'coverage/',
872
- '.nyc_output/',
873
- 'env.local'
874
- ];
875
-
876
- for (const pattern of requiredPatterns) {
877
- assert.ok(gitignore.includes(pattern), `Missing pattern: ${pattern}`);
878
- }
879
- });
package/tsconfig.json DELETED
@@ -1,31 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "lib": ["ES2022"],
7
- "declaration": true,
8
- "declarationMap": true,
9
- "sourceMap": true,
10
- "outDir": "./dist",
11
- "rootDir": ".",
12
- "strict": true,
13
- "noImplicitAny": true,
14
- "strictNullChecks": true,
15
- "strictFunctionTypes": true,
16
- "strictBindCallApply": true,
17
- "strictPropertyInitialization": true,
18
- "noImplicitThis": true,
19
- "alwaysStrict": true,
20
- "noUnusedLocals": true,
21
- "noUnusedParameters": true,
22
- "noImplicitReturns": true,
23
- "noFallthroughCasesInSwitch": true,
24
- "esModuleInterop": true,
25
- "skipLibCheck": true,
26
- "forceConsistentCasingInFileNames": true,
27
- "resolveJsonModule": true
28
- },
29
- "include": ["src/**/*", "test/**/*"],
30
- "exclude": ["node_modules", "dist"]
31
- }