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.
- package/bin/create-npkg.js +3 -0
- package/dist/src/index.js +0 -0
- package/package.json +9 -5
- package/src/index.ts +0 -468
- package/test/index.test.ts +0 -879
- package/tsconfig.json +0 -31
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.
|
|
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": "./
|
|
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
|
-
}
|
package/test/index.test.ts
DELETED
|
@@ -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
|
-
}
|