@monorepolint/rules 0.6.0-alpha.4 → 0.6.0-alpha.6
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/.turbo/turbo-clean.log +1 -1
- package/.turbo/turbo-compile-typescript.log +1 -1
- package/.turbo/turbo-lint.log +1 -1
- package/.turbo/turbo-test.log +443 -92
- package/.turbo/turbo-transpile-typescript.log +5 -5
- package/CHANGELOG.md +112 -0
- package/build/js/index.js +413 -368
- package/build/js/index.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/types/REMOVE.d.ts +2 -0
- package/build/types/REMOVE.d.ts.map +1 -0
- package/build/types/__tests__/alphabeticalDependencies.spec.d.ts +8 -0
- package/build/types/__tests__/alphabeticalDependencies.spec.d.ts.map +1 -0
- package/build/types/__tests__/forceError.spec.d.ts +8 -0
- package/build/types/__tests__/forceError.spec.d.ts.map +1 -0
- package/build/types/__tests__/oncePerPackage.spec.d.ts +8 -0
- package/build/types/__tests__/oncePerPackage.spec.d.ts.map +1 -0
- package/build/types/__tests__/standardTsconfig.spec.d.ts +8 -0
- package/build/types/__tests__/standardTsconfig.spec.d.ts.map +1 -0
- package/build/types/bannedDependencies.d.ts +9 -33
- package/build/types/bannedDependencies.d.ts.map +1 -1
- package/build/types/consistentDependencies.d.ts +6 -6
- package/build/types/consistentDependencies.d.ts.map +1 -1
- package/build/types/consistentVersions.d.ts +6 -10
- package/build/types/consistentVersions.d.ts.map +1 -1
- package/build/types/fileContents.d.ts +3 -2
- package/build/types/fileContents.d.ts.map +1 -1
- package/build/types/index.d.ts +1 -0
- package/build/types/index.d.ts.map +1 -1
- package/build/types/mustSatisfyPeerDependencies.d.ts +12 -190
- package/build/types/mustSatisfyPeerDependencies.d.ts.map +1 -1
- package/build/types/nestedWorkspaces.d.ts +2 -2
- package/build/types/nestedWorkspaces.d.ts.map +1 -1
- package/build/types/oncePerPackage.d.ts +6 -6
- package/build/types/oncePerPackage.d.ts.map +1 -1
- package/build/types/packageEntry.d.ts +11 -33
- package/build/types/packageEntry.d.ts.map +1 -1
- package/build/types/packageOrder.d.ts +2 -1
- package/build/types/packageOrder.d.ts.map +1 -1
- package/build/types/packageScript.d.ts +13 -22
- package/build/types/packageScript.d.ts.map +1 -1
- package/build/types/requireDependency.d.ts +5 -20
- package/build/types/requireDependency.d.ts.map +1 -1
- package/build/types/standardTsconfig.d.ts +12 -19
- package/build/types/standardTsconfig.d.ts.map +1 -1
- package/build/types/util/zodSchemas.d.ts +14 -0
- package/build/types/util/zodSchemas.d.ts.map +1 -0
- package/coverage/block-navigation.js +1 -1
- package/coverage/clover.xml +1420 -1452
- package/coverage/coverage-final.json +21 -19
- package/coverage/index.html +27 -27
- package/coverage/sorter.js +21 -7
- package/coverage/src/REMOVE.ts.html +88 -0
- package/coverage/src/alphabeticalDependencies.ts.html +15 -15
- package/coverage/src/alphabeticalScripts.ts.html +5 -5
- package/coverage/src/bannedDependencies.ts.html +20 -53
- package/coverage/src/consistentDependencies.ts.html +20 -14
- package/coverage/src/consistentVersions.ts.html +330 -183
- package/coverage/src/fileContents.ts.html +223 -88
- package/coverage/src/forceError.ts.html +31 -31
- package/coverage/src/index.html +104 -89
- package/coverage/src/index.ts.html +11 -5
- package/coverage/src/mustSatisfyPeerDependencies.ts.html +15 -501
- package/coverage/src/nestedWorkspaces.ts.html +5 -5
- package/coverage/src/oncePerPackage.ts.html +31 -31
- package/coverage/src/packageEntry.ts.html +121 -91
- package/coverage/src/packageOrder.ts.html +44 -14
- package/coverage/src/packageScript.ts.html +235 -88
- package/coverage/src/requireDependency.ts.html +241 -82
- package/coverage/src/standardTsconfig.ts.html +212 -212
- package/coverage/src/util/checkAlpha.ts.html +40 -40
- package/coverage/src/util/createRuleFactory.ts.html +19 -19
- package/coverage/src/util/index.html +30 -15
- package/coverage/src/util/makeDirectory.ts.html +11 -11
- package/coverage/src/util/packageDependencyGraphService.ts.html +1 -1
- package/coverage/src/util/zodSchemas.ts.html +130 -0
- package/package.json +15 -15
- package/src/REMOVE.ts +1 -0
- package/src/__tests__/alphabeticalDependencies.spec.ts +102 -0
- package/src/__tests__/alphabeticalScripts.spec.ts +18 -0
- package/src/__tests__/bannedDependencies.spec.ts +49 -0
- package/src/__tests__/consistentDependencies.spec.ts +23 -0
- package/src/__tests__/consistentVersions.spec.ts +142 -0
- package/src/__tests__/fileContents.spec.ts +348 -0
- package/src/__tests__/forceError.spec.ts +70 -0
- package/src/__tests__/mustSatisfyPeerDependencies.spec.ts +44 -0
- package/src/__tests__/nestedWorkspaces.spec.ts +14 -0
- package/src/__tests__/oncePerPackage.spec.ts +75 -0
- package/src/__tests__/packageEntry.spec.ts +177 -0
- package/src/__tests__/packageOrder.spec.ts +22 -0
- package/src/__tests__/packageScript.spec.ts +549 -0
- package/src/__tests__/requireDependency.spec.ts +259 -2
- package/src/__tests__/standardTsconfig.spec.ts +91 -0
- package/src/bannedDependencies.ts +14 -25
- package/src/consistentDependencies.ts +10 -8
- package/src/consistentVersions.ts +132 -83
- package/src/fileContents.ts +80 -35
- package/src/index.ts +2 -0
- package/src/mustSatisfyPeerDependencies.ts +10 -172
- package/src/nestedWorkspaces.ts +4 -4
- package/src/oncePerPackage.ts +6 -6
- package/src/packageEntry.ts +60 -50
- package/src/packageOrder.ts +19 -9
- package/src/packageScript.ts +67 -18
- package/src/requireDependency.ts +84 -31
- package/src/standardTsconfig.ts +26 -26
- package/src/util/zodSchemas.ts +15 -0
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { Context, Failure } from "@monorepolint/config";
|
|
10
10
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
11
11
|
import { packageScript } from "../packageScript.js";
|
|
12
|
+
import { REMOVE } from "../REMOVE.js";
|
|
12
13
|
import { AddErrorSpy, createTestingWorkspace, HOST_FACTORIES, TestingWorkspace } from "./utils.js";
|
|
13
14
|
|
|
14
15
|
const json = (a: unknown) => JSON.stringify(a, undefined, 2) + "\n";
|
|
@@ -252,4 +253,552 @@ describe.each(HOST_FACTORIES)("expectPackageScript ($name)", (hostFactory) => {
|
|
|
252
253
|
);
|
|
253
254
|
});
|
|
254
255
|
});
|
|
256
|
+
|
|
257
|
+
describe("Missing package.json handling", () => {
|
|
258
|
+
let workspace: TestingWorkspace;
|
|
259
|
+
|
|
260
|
+
beforeEach(async () => {
|
|
261
|
+
workspace = await createTestingWorkspace({
|
|
262
|
+
fixFlag: false,
|
|
263
|
+
host: hostFactory.make(),
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("handles gracefully when package.json does not exist", () => {
|
|
268
|
+
// Don't create a package.json file in the current directory
|
|
269
|
+
// Create a child context that points to a directory without package.json
|
|
270
|
+
const childContext = workspace.context.getWorkspaceContext().createChildContext(
|
|
271
|
+
workspace.getFilePath("packages/missing"),
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
expect(() => {
|
|
275
|
+
packageScript({
|
|
276
|
+
options: {
|
|
277
|
+
scripts: {
|
|
278
|
+
build: "tsc",
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
}).check(childContext);
|
|
282
|
+
}).toThrow(); // Should throw when trying to get package.json
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
describe("Invalid package.json structure", () => {
|
|
287
|
+
let workspace: TestingWorkspace;
|
|
288
|
+
|
|
289
|
+
beforeEach(async () => {
|
|
290
|
+
workspace = await createTestingWorkspace({
|
|
291
|
+
fixFlag: false,
|
|
292
|
+
host: hostFactory.make(),
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it("handles package.json with non-object scripts", () => {
|
|
297
|
+
workspace.writeFile(
|
|
298
|
+
"package.json",
|
|
299
|
+
json({
|
|
300
|
+
name: "test-package",
|
|
301
|
+
scripts: "invalid-scripts", // Should be an object
|
|
302
|
+
}),
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
// This actually won't throw - the rule will just add an error for missing scripts block
|
|
306
|
+
// since scripts is not an object, it will be treated as undefined
|
|
307
|
+
const spy = vi.spyOn(workspace.context, "addError");
|
|
308
|
+
|
|
309
|
+
packageScript({
|
|
310
|
+
options: {
|
|
311
|
+
scripts: {
|
|
312
|
+
build: "tsc",
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
}).check(workspace.context);
|
|
316
|
+
|
|
317
|
+
// Should add an error because scripts is not a proper object
|
|
318
|
+
expect(spy).toHaveBeenCalled();
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it("handles malformed JSON in package.json", () => {
|
|
322
|
+
workspace.writeFile("package.json", "{ invalid json }");
|
|
323
|
+
|
|
324
|
+
expect(() => {
|
|
325
|
+
packageScript({
|
|
326
|
+
options: {
|
|
327
|
+
scripts: {
|
|
328
|
+
build: "tsc",
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
}).check(workspace.context);
|
|
332
|
+
}).toThrow(); // Should throw when parsing invalid JSON
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
describe("REMOVE symbol usage", () => {
|
|
337
|
+
let workspace: TestingWorkspace;
|
|
338
|
+
let spy: AddErrorSpy;
|
|
339
|
+
let context: Context;
|
|
340
|
+
|
|
341
|
+
beforeEach(async () => {
|
|
342
|
+
workspace = await createTestingWorkspace({
|
|
343
|
+
fixFlag: true,
|
|
344
|
+
host: hostFactory.make(),
|
|
345
|
+
});
|
|
346
|
+
spy = vi.spyOn(workspace.context, "addError");
|
|
347
|
+
context = workspace.context;
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it("can remove a script using REMOVE in options array", () => {
|
|
351
|
+
workspace.writeFile("package.json", PACKAGE_WITH_SCRIPTS);
|
|
352
|
+
|
|
353
|
+
packageScript({
|
|
354
|
+
options: {
|
|
355
|
+
scripts: {
|
|
356
|
+
[SCRIPT_NAME]: {
|
|
357
|
+
options: ["different-value", REMOVE], // Current value doesn't match either option
|
|
358
|
+
fixValue: REMOVE,
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
}).check(context);
|
|
363
|
+
|
|
364
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
365
|
+
|
|
366
|
+
const failure: Failure = spy.mock.calls[0][0];
|
|
367
|
+
expect(failure).toMatchObject(
|
|
368
|
+
workspace.failureMatcher({
|
|
369
|
+
file: "package.json",
|
|
370
|
+
hasFixer: true,
|
|
371
|
+
message: expect.stringContaining(
|
|
372
|
+
`Expected standardized script entry for '${SCRIPT_NAME}'`,
|
|
373
|
+
) as unknown as string,
|
|
374
|
+
}),
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
// Verify script was removed
|
|
378
|
+
expect(JSON.parse(workspace.readFile("package.json")!).scripts).toEqual({});
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it("handles REMOVE on non-existent script", () => {
|
|
382
|
+
workspace.writeFile("package.json", PACKAGE_WITHOUT_SCRIPTS);
|
|
383
|
+
|
|
384
|
+
packageScript({
|
|
385
|
+
options: {
|
|
386
|
+
scripts: {
|
|
387
|
+
nonExistent: {
|
|
388
|
+
options: ["value", REMOVE],
|
|
389
|
+
fixValue: REMOVE,
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
}).check(context);
|
|
394
|
+
|
|
395
|
+
expect(spy).toHaveBeenCalledTimes(1); // Only one for no scripts block - the rule returns early
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
describe("Direct REMOVE syntax", () => {
|
|
400
|
+
let workspace: TestingWorkspace;
|
|
401
|
+
let spy: AddErrorSpy;
|
|
402
|
+
let context: Context;
|
|
403
|
+
|
|
404
|
+
beforeEach(async () => {
|
|
405
|
+
workspace = await createTestingWorkspace({
|
|
406
|
+
fixFlag: true,
|
|
407
|
+
host: hostFactory.make(),
|
|
408
|
+
});
|
|
409
|
+
spy = vi.spyOn(workspace.context, "addError");
|
|
410
|
+
context = workspace.context;
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it("removes existing script using direct REMOVE syntax", () => {
|
|
414
|
+
workspace.writeFile("package.json", PACKAGE_WITH_SCRIPTS);
|
|
415
|
+
|
|
416
|
+
packageScript({
|
|
417
|
+
options: {
|
|
418
|
+
scripts: {
|
|
419
|
+
[SCRIPT_NAME]: REMOVE,
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
}).check(context);
|
|
423
|
+
|
|
424
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
425
|
+
|
|
426
|
+
const failure: Failure = spy.mock.calls[0][0];
|
|
427
|
+
expect(failure).toMatchObject(
|
|
428
|
+
workspace.failureMatcher({
|
|
429
|
+
file: "package.json",
|
|
430
|
+
hasFixer: true,
|
|
431
|
+
message: `Script '${SCRIPT_NAME}' should be removed`,
|
|
432
|
+
}),
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
// Verify script was removed
|
|
436
|
+
expect(JSON.parse(workspace.readFile("package.json")!).scripts).toEqual({});
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it("does not error when REMOVE is specified for non-existent script", () => {
|
|
440
|
+
workspace.writeFile("package.json", PACKAGE_WITH_SCRIPTS);
|
|
441
|
+
|
|
442
|
+
packageScript({
|
|
443
|
+
options: {
|
|
444
|
+
scripts: {
|
|
445
|
+
nonExistentScript: REMOVE,
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
}).check(context);
|
|
449
|
+
|
|
450
|
+
expect(spy).toHaveBeenCalledTimes(0);
|
|
451
|
+
|
|
452
|
+
// Original scripts should remain unchanged
|
|
453
|
+
expect(JSON.parse(workspace.readFile("package.json")!).scripts).toEqual({
|
|
454
|
+
[SCRIPT_NAME]: SCRIPT_VALUE,
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it("handles mix of REMOVE and regular script values", () => {
|
|
459
|
+
const packageWithMultipleScripts = json({
|
|
460
|
+
name: "package-with-multiple-scripts",
|
|
461
|
+
scripts: {
|
|
462
|
+
build: "tsc",
|
|
463
|
+
test: "jest",
|
|
464
|
+
lint: "eslint",
|
|
465
|
+
},
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
workspace.writeFile("package.json", packageWithMultipleScripts);
|
|
469
|
+
|
|
470
|
+
packageScript({
|
|
471
|
+
options: {
|
|
472
|
+
scripts: {
|
|
473
|
+
build: REMOVE, // Remove existing script
|
|
474
|
+
test: "vitest", // Change existing script
|
|
475
|
+
start: "node index.js", // Add new script
|
|
476
|
+
lint: REMOVE, // Remove another existing script
|
|
477
|
+
},
|
|
478
|
+
},
|
|
479
|
+
}).check(context);
|
|
480
|
+
|
|
481
|
+
expect(spy).toHaveBeenCalledTimes(4); // build removal, test change, start addition, lint removal
|
|
482
|
+
|
|
483
|
+
const failures = spy.mock.calls.map(call => call[0]);
|
|
484
|
+
|
|
485
|
+
// We expect 4 failures: build removal, test change, start addition, lint removal
|
|
486
|
+
// The order might vary, so let's check by message content instead of position
|
|
487
|
+
const errorMessages = failures.map(f => f.message);
|
|
488
|
+
|
|
489
|
+
expect(errorMessages).toContain("Script 'build' should be removed");
|
|
490
|
+
expect(errorMessages).toContain("Script 'lint' should be removed");
|
|
491
|
+
expect(
|
|
492
|
+
errorMessages.some(msg => msg.includes("Expected standardized script entry for 'test'")),
|
|
493
|
+
).toBe(true);
|
|
494
|
+
expect(
|
|
495
|
+
errorMessages.some(msg => msg.includes("Expected standardized script entry for 'start'")),
|
|
496
|
+
).toBe(true);
|
|
497
|
+
|
|
498
|
+
// Verify final scripts state
|
|
499
|
+
expect(JSON.parse(workspace.readFile("package.json")!).scripts).toEqual({
|
|
500
|
+
test: "vitest",
|
|
501
|
+
start: "node index.js",
|
|
502
|
+
});
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it("handles REMOVE when scripts block doesn't exist", () => {
|
|
506
|
+
workspace.writeFile("package.json", PACKAGE_WITHOUT_SCRIPTS);
|
|
507
|
+
|
|
508
|
+
packageScript({
|
|
509
|
+
options: {
|
|
510
|
+
scripts: {
|
|
511
|
+
build: REMOVE,
|
|
512
|
+
},
|
|
513
|
+
},
|
|
514
|
+
}).check(context);
|
|
515
|
+
|
|
516
|
+
expect(spy).toHaveBeenCalledTimes(1); // Only for missing scripts block
|
|
517
|
+
|
|
518
|
+
const failure: Failure = spy.mock.calls[0][0];
|
|
519
|
+
expect(failure).toMatchObject(
|
|
520
|
+
workspace.failureMatcher({
|
|
521
|
+
file: "package.json",
|
|
522
|
+
hasFixer: true,
|
|
523
|
+
message: "No scripts block in package.json",
|
|
524
|
+
}),
|
|
525
|
+
);
|
|
526
|
+
});
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
describe("Advanced allowedValues scenarios", () => {
|
|
530
|
+
let workspace: TestingWorkspace;
|
|
531
|
+
let spy: AddErrorSpy;
|
|
532
|
+
let context: Context;
|
|
533
|
+
|
|
534
|
+
beforeEach(async () => {
|
|
535
|
+
workspace = await createTestingWorkspace({
|
|
536
|
+
fixFlag: true,
|
|
537
|
+
host: hostFactory.make(),
|
|
538
|
+
});
|
|
539
|
+
spy = vi.spyOn(workspace.context, "addError");
|
|
540
|
+
context = workspace.context;
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
it("handles multiple options without fixValue (no fixer should be provided)", () => {
|
|
544
|
+
workspace.writeFile("package.json", PACKAGE_WITH_SCRIPTS);
|
|
545
|
+
|
|
546
|
+
packageScript({
|
|
547
|
+
options: {
|
|
548
|
+
scripts: {
|
|
549
|
+
[SCRIPT_NAME]: {
|
|
550
|
+
options: ["option-a", "option-b", "option-c"],
|
|
551
|
+
// No fixValue specified
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
},
|
|
555
|
+
}).check(context);
|
|
556
|
+
|
|
557
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
558
|
+
|
|
559
|
+
const failure: Failure = spy.mock.calls[0][0];
|
|
560
|
+
expect(failure.fixer).toBeUndefined(); // No fixer when no fixValue
|
|
561
|
+
expect(failure.message).toContain("Expected standardized script entry");
|
|
562
|
+
expect(failure.message).toContain("option-a");
|
|
563
|
+
expect(failure.message).toContain("option-b");
|
|
564
|
+
expect(failure.message).toContain("option-c");
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
it("handles mixed option types: string + undefined + REMOVE", () => {
|
|
568
|
+
workspace.writeFile("package.json", PACKAGE_WITH_SCRIPTS);
|
|
569
|
+
|
|
570
|
+
packageScript({
|
|
571
|
+
options: {
|
|
572
|
+
scripts: {
|
|
573
|
+
[SCRIPT_NAME]: {
|
|
574
|
+
options: ["build-cmd", undefined, REMOVE],
|
|
575
|
+
fixValue: "build-cmd",
|
|
576
|
+
},
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
}).check(context);
|
|
580
|
+
|
|
581
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
582
|
+
|
|
583
|
+
const failure: Failure = spy.mock.calls[0][0];
|
|
584
|
+
expect(failure.fixer).toBeDefined();
|
|
585
|
+
expect(failure.message).toContain("Expected standardized script entry");
|
|
586
|
+
expect(failure.message).toContain("build-cmd");
|
|
587
|
+
expect(failure.message).toContain("(empty)"); // Should show undefined/REMOVE as (empty)
|
|
588
|
+
|
|
589
|
+
// Verify it fixes to the fixValue
|
|
590
|
+
expect(JSON.parse(workspace.readFile("package.json")!).scripts[SCRIPT_NAME]).toBe(
|
|
591
|
+
"build-cmd",
|
|
592
|
+
);
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
it("allows empty when REMOVE is in options array", () => {
|
|
596
|
+
const packageWithoutSpecificScript = json({
|
|
597
|
+
name: "test-package",
|
|
598
|
+
scripts: {
|
|
599
|
+
"other-script": "other-value",
|
|
600
|
+
},
|
|
601
|
+
});
|
|
602
|
+
workspace.writeFile("package.json", packageWithoutSpecificScript);
|
|
603
|
+
|
|
604
|
+
packageScript({
|
|
605
|
+
options: {
|
|
606
|
+
scripts: {
|
|
607
|
+
"missing-script": {
|
|
608
|
+
options: ["some-value", REMOVE],
|
|
609
|
+
fixValue: "some-value",
|
|
610
|
+
},
|
|
611
|
+
},
|
|
612
|
+
},
|
|
613
|
+
}).check(context);
|
|
614
|
+
|
|
615
|
+
// When REMOVE is in options array, it sets allowEmpty=true
|
|
616
|
+
// So missing script (undefined) should NOT error - this is correct behavior
|
|
617
|
+
expect(spy).toHaveBeenCalledTimes(0);
|
|
618
|
+
|
|
619
|
+
// Original state should remain unchanged since no error occurred
|
|
620
|
+
const finalScripts = JSON.parse(workspace.readFile("package.json")!).scripts;
|
|
621
|
+
expect(finalScripts["missing-script"]).toBeUndefined();
|
|
622
|
+
expect(finalScripts["other-script"]).toBe("other-value");
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
it("handles existing script with REMOVE in options array", () => {
|
|
626
|
+
workspace.writeFile("package.json", PACKAGE_WITH_SCRIPTS);
|
|
627
|
+
|
|
628
|
+
packageScript({
|
|
629
|
+
options: {
|
|
630
|
+
scripts: {
|
|
631
|
+
[SCRIPT_NAME]: {
|
|
632
|
+
options: ["different-value", REMOVE], // Current value doesn't match "different-value"
|
|
633
|
+
fixValue: REMOVE, // Should fix by removing the script
|
|
634
|
+
},
|
|
635
|
+
},
|
|
636
|
+
},
|
|
637
|
+
}).check(context);
|
|
638
|
+
|
|
639
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
640
|
+
|
|
641
|
+
const failure: Failure = spy.mock.calls[0][0];
|
|
642
|
+
expect(failure.fixer).toBeDefined();
|
|
643
|
+
expect(failure.message).toContain("Expected standardized script entry");
|
|
644
|
+
|
|
645
|
+
// Should be removed since fixValue is REMOVE
|
|
646
|
+
expect(JSON.parse(workspace.readFile("package.json")!).scripts[SCRIPT_NAME]).toBeUndefined();
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
it("handles fixValue: false (prevents fixing)", () => {
|
|
650
|
+
workspace.writeFile("package.json", PACKAGE_WITH_SCRIPTS);
|
|
651
|
+
|
|
652
|
+
packageScript({
|
|
653
|
+
options: {
|
|
654
|
+
scripts: {
|
|
655
|
+
[SCRIPT_NAME]: {
|
|
656
|
+
options: ["different-value"],
|
|
657
|
+
fixValue: false,
|
|
658
|
+
},
|
|
659
|
+
},
|
|
660
|
+
},
|
|
661
|
+
}).check(context);
|
|
662
|
+
|
|
663
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
664
|
+
|
|
665
|
+
const failure: Failure = spy.mock.calls[0][0];
|
|
666
|
+
expect(failure.fixer).toBeUndefined(); // fixValue: false should prevent fixer
|
|
667
|
+
expect(failure.message).toContain("Expected standardized script entry");
|
|
668
|
+
|
|
669
|
+
// Original value should remain unchanged
|
|
670
|
+
expect(JSON.parse(workspace.readFile("package.json")!).scripts[SCRIPT_NAME]).toBe(
|
|
671
|
+
SCRIPT_VALUE,
|
|
672
|
+
);
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
it("formats error messages correctly for complex allowedValues", () => {
|
|
676
|
+
workspace.writeFile("package.json", PACKAGE_WITH_SCRIPTS);
|
|
677
|
+
|
|
678
|
+
packageScript({
|
|
679
|
+
options: {
|
|
680
|
+
scripts: {
|
|
681
|
+
[SCRIPT_NAME]: {
|
|
682
|
+
options: ["cmd-a", "cmd-b", undefined, REMOVE],
|
|
683
|
+
fixValue: "cmd-a",
|
|
684
|
+
},
|
|
685
|
+
},
|
|
686
|
+
},
|
|
687
|
+
}).check(context);
|
|
688
|
+
|
|
689
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
690
|
+
|
|
691
|
+
const failure: Failure = spy.mock.calls[0][0];
|
|
692
|
+
const message = failure.message;
|
|
693
|
+
|
|
694
|
+
// Should contain all allowed values in the main message
|
|
695
|
+
expect(message).toContain("'cmd-a'");
|
|
696
|
+
expect(message).toContain("'cmd-b'");
|
|
697
|
+
expect(message).toContain("(empty)"); // Both undefined and REMOVE should show as (empty)
|
|
698
|
+
|
|
699
|
+
// The longMessage contains a diff between expected and actual values
|
|
700
|
+
expect(failure.longMessage).toBeDefined();
|
|
701
|
+
expect(failure.longMessage).toContain("'cmd-a', 'cmd-b', (empty), (empty)"); // This is the expected part of the diff
|
|
702
|
+
expect(failure.longMessage).toContain(SCRIPT_VALUE); // This is the actual current value
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
it("processes multiple scripts with different allowedValues configurations", () => {
|
|
706
|
+
const complexPackage = json({
|
|
707
|
+
name: "complex-package",
|
|
708
|
+
scripts: {
|
|
709
|
+
"script-a": "wrong-value-a",
|
|
710
|
+
"script-b": "wrong-value-b",
|
|
711
|
+
"script-c": "correct-value-c",
|
|
712
|
+
},
|
|
713
|
+
});
|
|
714
|
+
workspace.writeFile("package.json", complexPackage);
|
|
715
|
+
|
|
716
|
+
packageScript({
|
|
717
|
+
options: {
|
|
718
|
+
scripts: {
|
|
719
|
+
"script-a": {
|
|
720
|
+
options: ["correct-a1", "correct-a2"],
|
|
721
|
+
fixValue: "correct-a1",
|
|
722
|
+
},
|
|
723
|
+
"script-b": {
|
|
724
|
+
options: ["correct-b", undefined],
|
|
725
|
+
fixValue: undefined, // Fix to empty (removal)
|
|
726
|
+
},
|
|
727
|
+
"script-c": "correct-value-c", // Already correct, should not error
|
|
728
|
+
"script-d": REMOVE, // Doesn't exist, should not error
|
|
729
|
+
},
|
|
730
|
+
},
|
|
731
|
+
}).check(context);
|
|
732
|
+
|
|
733
|
+
expect(spy).toHaveBeenCalledTimes(2); // Only script-a and script-b should error
|
|
734
|
+
|
|
735
|
+
const failures = spy.mock.calls.map(call => call[0]);
|
|
736
|
+
const messages = failures.map(f => f.message);
|
|
737
|
+
|
|
738
|
+
expect(messages.some(msg => msg.includes("script-a"))).toBe(true);
|
|
739
|
+
expect(messages.some(msg => msg.includes("script-b"))).toBe(true);
|
|
740
|
+
expect(messages.some(msg => msg.includes("script-c"))).toBe(false); // Should not error
|
|
741
|
+
expect(messages.some(msg => msg.includes("script-d"))).toBe(false); // Should not error
|
|
742
|
+
|
|
743
|
+
// Verify final state
|
|
744
|
+
const finalScripts = JSON.parse(workspace.readFile("package.json")!).scripts;
|
|
745
|
+
expect(finalScripts["script-a"]).toBe("correct-a1");
|
|
746
|
+
expect(finalScripts["script-b"]).toBeUndefined(); // Should be removed
|
|
747
|
+
expect(finalScripts["script-c"]).toBe("correct-value-c"); // Unchanged
|
|
748
|
+
expect(finalScripts["script-d"]).toBeUndefined(); // Still doesn't exist
|
|
749
|
+
});
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
describe("Options Validation", () => {
|
|
753
|
+
it("should accept valid options", () => {
|
|
754
|
+
const ruleModule = packageScript({
|
|
755
|
+
options: { scripts: { "build": "tsc" } },
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
expect(() =>
|
|
759
|
+
ruleModule.validateOptions({
|
|
760
|
+
scripts: {
|
|
761
|
+
"build": "tsc",
|
|
762
|
+
"test": {
|
|
763
|
+
options: ["jest", "vitest", undefined],
|
|
764
|
+
fixValue: "jest",
|
|
765
|
+
},
|
|
766
|
+
},
|
|
767
|
+
})
|
|
768
|
+
).not.toThrow();
|
|
769
|
+
|
|
770
|
+
expect(() =>
|
|
771
|
+
ruleModule.validateOptions({
|
|
772
|
+
scripts: {
|
|
773
|
+
"start": "node index.js",
|
|
774
|
+
"outdated": REMOVE, // Direct REMOVE syntax
|
|
775
|
+
},
|
|
776
|
+
})
|
|
777
|
+
).not.toThrow();
|
|
778
|
+
|
|
779
|
+
expect(() =>
|
|
780
|
+
ruleModule.validateOptions({
|
|
781
|
+
scripts: {
|
|
782
|
+
"build": REMOVE,
|
|
783
|
+
"test": "jest",
|
|
784
|
+
"lint": {
|
|
785
|
+
options: ["eslint", REMOVE],
|
|
786
|
+
fixValue: REMOVE,
|
|
787
|
+
},
|
|
788
|
+
},
|
|
789
|
+
})
|
|
790
|
+
).not.toThrow();
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
it("should reject invalid options", () => {
|
|
794
|
+
const ruleModule = packageScript({ options: { scripts: { "build": "tsc" } } });
|
|
795
|
+
|
|
796
|
+
// @ts-expect-error testing invalid input
|
|
797
|
+
expect(() => ruleModule.validateOptions({})).toThrow();
|
|
798
|
+
// @ts-expect-error testing invalid input
|
|
799
|
+
expect(() => ruleModule.validateOptions({ scripts: { "build": 123 } })).toThrow();
|
|
800
|
+
// @ts-expect-error testing invalid input
|
|
801
|
+
expect(() => ruleModule.validateOptions({ scripts: "invalid" })).toThrow();
|
|
802
|
+
});
|
|
803
|
+
});
|
|
255
804
|
});
|