create-npkg 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +191 -0
- package/dist/src/index.d.ts +61 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +417 -0
- package/dist/src/index.js.map +1 -0
- package/package.json +42 -0
- package/src/index.ts +468 -0
- package/test/index.test.ts +879 -0
- package/tsconfig.json +31 -0
|
@@ -0,0 +1,879 @@
|
|
|
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
|
+
});
|