1const t = require('tap') 2const fs = require('fs/promises') 3const { resolve, basename } = require('path') 4const _mockNpm = require('../../fixtures/mock-npm') 5const { cleanTime } = require('../../fixtures/clean-snapshot') 6 7t.cleanSnapshot = cleanTime 8 9const mockNpm = async (t, { noLog, libnpmexec, initPackageJson, ...opts } = {}) => { 10 const res = await _mockNpm(t, { 11 ...opts, 12 mocks: { 13 ...(libnpmexec ? { libnpmexec } : {}), 14 ...(initPackageJson ? { 'init-package-json': initPackageJson } : {}), 15 }, 16 globals: { 17 // init-package-json prints directly to console.log 18 // this avoids poluting test output with those logs 19 ...(noLog ? { 'console.log': () => {} } : {}), 20 }, 21 }) 22 23 return res 24} 25 26t.test('displays output', async t => { 27 const { npm, joinedOutput } = await mockNpm(t, { 28 initPackageJson: async () => {}, 29 }) 30 31 await npm.exec('init', []) 32 t.matchSnapshot(joinedOutput(), 'displays helper info') 33}) 34 35t.test('classic npm init -y', async t => { 36 const { npm, prefix } = await mockNpm(t, { 37 config: { yes: true }, 38 noLog: true, 39 }) 40 41 await npm.exec('init', []) 42 43 const pkg = require(resolve(prefix, 'package.json')) 44 t.equal(pkg.version, '1.0.0') 45 t.equal(pkg.license, 'ISC') 46}) 47 48t.test('classic interactive npm init', async t => { 49 t.plan(1) 50 51 const { npm } = await mockNpm(t, { 52 initPackageJson: async (path) => { 53 t.equal( 54 path, 55 resolve(npm.localPrefix), 56 'should start init package.json in expected path' 57 ) 58 }, 59 }) 60 61 await npm.exec('init', []) 62}) 63 64t.test('npm init <arg>', async t => { 65 t.plan(1) 66 67 const { npm } = await mockNpm(t, { 68 libnpmexec: ({ args }) => { 69 t.same( 70 args, 71 ['create-react-app@*'], 72 'should npx with listed packages' 73 ) 74 }, 75 }) 76 77 await npm.exec('init', ['react-app']) 78}) 79 80t.test('npm init <arg> -- other-args', async t => { 81 t.plan(1) 82 83 const { npm } = await mockNpm(t, { 84 libnpmexec: ({ args }) => { 85 t.same( 86 args, 87 ['create-react-app@*', 'my-path', '--some-option', 'some-value'], 88 'should npm exec with expected args' 89 ) 90 }, 91 92 }) 93 94 await npm.exec('init', ['react-app', 'my-path', '--some-option', 'some-value']) 95}) 96 97t.test('npm init @scope/name', async t => { 98 t.plan(1) 99 100 const { npm } = await mockNpm(t, { 101 libnpmexec: ({ args }) => { 102 t.same( 103 args, 104 ['@npmcli/create-something@*'], 105 'should npx with scoped packages' 106 ) 107 }, 108 }) 109 110 await npm.exec('init', ['@npmcli/something']) 111}) 112 113t.test('npm init @scope@spec', async t => { 114 t.plan(1) 115 116 const { npm } = await mockNpm(t, { 117 libnpmexec: ({ args }) => { 118 t.same( 119 args, 120 ['@npmcli/create@foo'], 121 'should npx with scoped packages' 122 ) 123 }, 124 }) 125 126 await npm.exec('init', ['@npmcli@foo']) 127}) 128 129t.test('npm init @scope/name@spec', async t => { 130 t.plan(1) 131 132 const { npm } = await mockNpm(t, { 133 libnpmexec: ({ args }) => { 134 t.same( 135 args, 136 ['@npmcli/create-something@foo'], 137 'should npx with scoped packages' 138 ) 139 }, 140 }) 141 142 await npm.exec('init', ['@npmcli/something@foo']) 143}) 144 145t.test('npm init git spec', async t => { 146 t.plan(1) 147 const { npm } = await mockNpm(t, { 148 libnpmexec: ({ args }) => { 149 t.same( 150 args, 151 ['npm/create-something'], 152 'should npx with git-spec packages' 153 ) 154 }, 155 }) 156 157 await npm.exec('init', ['npm/something']) 158}) 159 160t.test('npm init @scope', async t => { 161 t.plan(1) 162 163 const { npm } = await mockNpm(t, { 164 libnpmexec: ({ args }) => { 165 t.same( 166 args, 167 ['@npmcli/create'], 168 'should npx with @scope/create pkgs' 169 ) 170 }, 171 }) 172 173 await npm.exec('init', ['@npmcli']) 174}) 175 176t.test('npm init tgz', async t => { 177 const { npm } = await mockNpm(t) 178 179 await t.rejects( 180 npm.exec('init', ['something.tgz']), 181 /Unrecognized initializer: something.tgz/, 182 'should throw error when using an unsupported spec' 183 ) 184}) 185 186t.test('npm init <arg>@next', async t => { 187 t.plan(1) 188 189 const { npm } = await mockNpm(t, { 190 libnpmexec: ({ args }) => { 191 t.same( 192 args, 193 ['create-something@next'], 194 'should npx with something@next' 195 ) 196 }, 197 }) 198 199 await npm.exec('init', ['something@next']) 200}) 201 202t.test('npm init exec error', async t => { 203 const { npm } = await mockNpm(t, { 204 libnpmexec: async () => { 205 throw new Error('ERROR') 206 }, 207 }) 208 209 await t.rejects( 210 npm.exec('init', ['something@next']), 211 /ERROR/, 212 'should exit with exec error' 213 ) 214}) 215 216t.test('should not rewrite flatOptions', async t => { 217 t.plan(1) 218 219 const { npm } = await mockNpm(t, { 220 libnpmexec: async ({ args }) => { 221 t.same( 222 args, 223 ['create-react-app@*', 'my-app'], 224 'should npx with extra args' 225 ) 226 }, 227 }) 228 229 await npm.exec('init', ['react-app', 'my-app']) 230}) 231 232t.test('npm init cancel', async t => { 233 const { npm, logs } = await mockNpm(t, { 234 initPackageJson: async () => { 235 throw new Error('canceled') 236 }, 237 }) 238 239 await npm.exec('init', []) 240 241 t.equal(logs.warn[0][0], 'init', 'should have init title') 242 t.equal(logs.warn[0][1], 'canceled', 'should log canceled') 243}) 244 245t.test('npm init error', async t => { 246 const { npm } = await mockNpm(t, { 247 initPackageJson: async () => { 248 throw new Error('Unknown Error') 249 }, 250 }) 251 252 await t.rejects( 253 npm.exec('init', []), 254 /Unknown Error/, 255 'should throw error' 256 ) 257}) 258 259t.test('workspaces', async t => { 260 await t.test('no args -- yes', async t => { 261 const { npm, prefix, joinedOutput } = await mockNpm(t, { 262 prefixDir: { 263 'package.json': JSON.stringify({ 264 name: 'top-level', 265 }), 266 }, 267 config: { workspace: 'a', yes: true }, 268 noLog: true, 269 }) 270 271 await npm.exec('init', []) 272 273 const pkg = require(resolve(prefix, 'a/package.json')) 274 t.equal(pkg.name, 'a') 275 t.equal(pkg.version, '1.0.0') 276 t.equal(pkg.license, 'ISC') 277 278 t.matchSnapshot(joinedOutput(), 'should print helper info') 279 280 const lock = require(resolve(prefix, 'package-lock.json')) 281 t.ok(lock.packages.a) 282 }) 283 284 await t.test('no args, existing folder', async t => { 285 const { npm, prefix } = await mockNpm(t, { 286 prefixDir: { 287 packages: { 288 a: { 289 'package.json': JSON.stringify({ 290 name: 'a', 291 version: '2.0.0', 292 }), 293 }, 294 }, 295 'package.json': JSON.stringify({ 296 name: 'top-level', 297 workspaces: ['packages/a'], 298 }), 299 }, 300 config: { workspace: 'packages/a', yes: true }, 301 noLog: true, 302 }) 303 304 await npm.exec('init', []) 305 306 const pkg = require(resolve(prefix, 'packages/a/package.json')) 307 t.equal(pkg.name, 'a') 308 t.equal(pkg.version, '2.0.0') 309 t.equal(pkg.license, 'ISC') 310 }) 311 312 await t.test('fail parsing top-level package.json to set workspace', async t => { 313 const { npm } = await mockNpm(t, { 314 prefixDir: { 315 'package.json': 'not json[', 316 }, 317 config: { workspace: 'a', yes: true }, 318 noLog: true, 319 }) 320 321 await t.rejects( 322 npm.exec('init', []), 323 { code: 'EJSONPARSE' } 324 ) 325 }) 326 327 await t.test('missing top-level package.json when settting workspace', async t => { 328 const { npm, logs } = await mockNpm(t, { 329 config: { workspace: 'a' }, 330 }) 331 332 await t.rejects( 333 npm.exec('init', []), 334 { code: 'ENOENT' }, 335 'should exit with missing package.json file error' 336 ) 337 338 t.equal(logs.warn[0][0], 'Missing package.json. Try with `--include-workspace-root`.') 339 }) 340 341 await t.test('bad package.json when settting workspace', async t => { 342 const { npm, logs } = await mockNpm(t, { 343 prefixDir: { 344 'package.json': '{{{{', 345 }, 346 config: { workspace: 'a' }, 347 }) 348 349 await t.rejects( 350 npm.exec('init', []), 351 { code: 'EJSONPARSE' }, 352 'should exit with parse file error' 353 ) 354 355 t.strictSame(logs.warn, []) 356 }) 357 358 await t.test('using args - no package.json', async t => { 359 const { npm, prefix } = await mockNpm(t, { 360 prefixDir: { 361 b: { 362 'package.json': JSON.stringify({ 363 name: 'b', 364 }), 365 }, 366 'package.json': JSON.stringify({ 367 name: 'top-level', 368 workspaces: ['b'], 369 }), 370 }, 371 // Important: exec did not write a package.json here 372 libnpmexec: async () => {}, 373 config: { workspace: 'a', yes: true }, 374 }) 375 376 await npm.exec('init', ['react-app']) 377 378 const pkg = require(resolve(prefix, 'package.json')) 379 t.strictSame(pkg.workspaces, ['b'], 'pkg workspaces did not get updated') 380 }) 381 382 await t.test('init template - bad package.json', async t => { 383 const { npm, prefix } = await mockNpm(t, { 384 prefixDir: { 385 b: { 386 'package.json': JSON.stringify({ 387 name: 'b', 388 }), 389 }, 390 'package.json': JSON.stringify({ 391 name: 'top-level', 392 workspaces: ['b'], 393 }), 394 }, 395 initPackageJson: async (...args) => { 396 const [dir] = args 397 if (dir.endsWith('c')) { 398 await fs.writeFile(resolve(dir, 'package.json'), JSON.stringify({ 399 name: basename(dir), 400 }), 'utf-8') 401 } 402 }, 403 config: { yes: true, workspace: ['a', 'c'] }, 404 }) 405 406 await npm.exec('init', []) 407 408 const pkg = require(resolve(prefix, 'package.json')) 409 t.strictSame(pkg.workspaces, ['b', 'c']) 410 411 const lock = require(resolve(prefix, 'package-lock.json')) 412 t.notOk(lock.packages.a) 413 }) 414 415 t.test('workspace root', async t => { 416 const { npm } = await mockNpm(t, { 417 config: { workspace: 'packages/a', 'include-workspace-root': true, yes: true }, 418 noLog: true, 419 }) 420 421 await npm.exec('init', []) 422 423 const pkg = require(resolve(npm.localPrefix, 'package.json')) 424 t.equal(pkg.version, '1.0.0') 425 t.equal(pkg.license, 'ISC') 426 t.strictSame(pkg.workspaces, ['packages/a']) 427 428 const ws = require(resolve(npm.localPrefix, 'packages/a/package.json')) 429 t.equal(ws.version, '1.0.0') 430 t.equal(ws.license, 'ISC') 431 }) 432}) 433