@smoothbricks/cli 0.2.0 → 0.3.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.
Files changed (68) hide show
  1. package/README.md +29 -24
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +19 -3
  4. package/dist/generate/index.d.ts +20 -0
  5. package/dist/generate/index.d.ts.map +1 -0
  6. package/dist/generate/index.js +47 -0
  7. package/dist/lib/run.d.ts +1 -0
  8. package/dist/lib/run.d.ts.map +1 -1
  9. package/dist/lib/run.js +19 -0
  10. package/dist/lib/workspace.d.ts +10 -0
  11. package/dist/lib/workspace.d.ts.map +1 -1
  12. package/dist/lib/workspace.js +55 -16
  13. package/dist/monorepo/lockfile.d.ts.map +1 -1
  14. package/dist/monorepo/lockfile.js +9 -0
  15. package/dist/monorepo/package-policy.d.ts +6 -17
  16. package/dist/monorepo/package-policy.d.ts.map +1 -1
  17. package/dist/monorepo/package-policy.js +63 -1127
  18. package/dist/monorepo/publish-workflow.js +2 -3
  19. package/dist/nx/index.d.ts +1 -0
  20. package/dist/nx/index.d.ts.map +1 -1
  21. package/dist/nx/index.js +23 -5
  22. package/dist/release/candidates.d.ts +1 -0
  23. package/dist/release/candidates.d.ts.map +1 -1
  24. package/dist/release/candidates.js +12 -0
  25. package/dist/release/core.d.ts +3 -1
  26. package/dist/release/core.d.ts.map +1 -1
  27. package/dist/release/core.js +76 -9
  28. package/dist/release/github-release.d.ts +1 -0
  29. package/dist/release/github-release.d.ts.map +1 -1
  30. package/dist/release/github-release.js +1 -1
  31. package/dist/release/index.d.ts +9 -3
  32. package/dist/release/index.d.ts.map +1 -1
  33. package/dist/release/index.js +287 -39
  34. package/dist/release/orchestration.d.ts +10 -2
  35. package/dist/release/orchestration.d.ts.map +1 -1
  36. package/dist/release/orchestration.js +33 -3
  37. package/managed/raw/tooling/git-hooks/commit-msg.sh +7 -1
  38. package/managed/raw/tooling/git-hooks/pre-commit.sh +7 -1
  39. package/managed/templates/github/workflows/ci.yml +3 -2
  40. package/package.json +7 -12
  41. package/src/cli.ts +21 -4
  42. package/src/generate/index.ts +92 -0
  43. package/src/lib/run.ts +25 -0
  44. package/src/lib/workspace.ts +72 -16
  45. package/src/monorepo/lockfile.ts +9 -0
  46. package/src/monorepo/package-policy.test.ts +254 -26
  47. package/src/monorepo/package-policy.ts +88 -1351
  48. package/src/monorepo/publish-workflow.ts +2 -3
  49. package/src/nx/index.test.ts +9 -0
  50. package/src/nx/index.ts +25 -5
  51. package/src/release/__tests__/candidates.test.ts +109 -8
  52. package/src/release/__tests__/core-properties.test.ts +2 -2
  53. package/src/release/__tests__/core-scenarios.test.ts +12 -15
  54. package/src/release/__tests__/core.test.ts +50 -3
  55. package/src/release/__tests__/fixture-repo.test.ts +68 -18
  56. package/src/release/__tests__/github-release.test.ts +9 -16
  57. package/src/release/__tests__/orchestration.test.ts +91 -5
  58. package/src/release/__tests__/retag-unpublished.test.ts +9 -9
  59. package/src/release/__tests__/trust-publisher.test.ts +65 -33
  60. package/src/release/candidates.ts +13 -0
  61. package/src/release/core.ts +95 -10
  62. package/src/release/github-release.ts +1 -1
  63. package/src/release/index.ts +366 -49
  64. package/src/release/orchestration.ts +50 -5
  65. package/dist/nx-version-actions.cjs +0 -25
  66. package/dist/nx-version-actions.d.cts +0 -6
  67. package/dist/nx-version-actions.d.cts.map +0 -1
  68. package/src/nx-version-actions.cts +0 -36
@@ -2,6 +2,11 @@ import { afterEach, describe, expect, it, spyOn } from 'bun:test';
2
2
  import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
3
3
  import { tmpdir } from 'node:os';
4
4
  import { dirname, join } from 'node:path';
5
+ import {
6
+ BOUNDED_TEST_EXECUTOR,
7
+ BOUNDED_TEST_KILL_AFTER_MS,
8
+ BOUNDED_TEST_TIMEOUT_MS,
9
+ } from '@smoothbricks/nx-plugin/bounded-test-policy';
5
10
  import {
6
11
  applyFixableMonorepoDefaults,
7
12
  applyNxPluginDefaults,
@@ -14,6 +19,20 @@ import {
14
19
  validateWorkspaceDependencies,
15
20
  } from './package-policy.js';
16
21
 
22
+ const buildOutputDependencies = [
23
+ '^build',
24
+ '*-js',
25
+ '*-web',
26
+ '*-html',
27
+ '*-css',
28
+ '*-ios',
29
+ '*-android',
30
+ '*-native',
31
+ '*-napi',
32
+ '*-bun',
33
+ '*-wasm',
34
+ ];
35
+
17
36
  describe('root smoo monorepo policy', () => {
18
37
  afterEach(() => {
19
38
  console.error = originalConsoleError;
@@ -216,6 +235,26 @@ describe('Nx project name policy', () => {
216
235
  await rm(root, { recursive: true, force: true });
217
236
  }
218
237
  });
238
+
239
+ it('accepts private unversioned workspace projects as commit scopes', async () => {
240
+ const root = await createWorkspace({
241
+ rootName: '@smoothbricks/codebase',
242
+ packages: [{ dir: 'tooling', name: '@smoothbricks/tooling', private: true, version: null }],
243
+ });
244
+ try {
245
+ expect(validateNxProjectNames(root)).toBe(1);
246
+
247
+ applyNxProjectNameDefaults(root);
248
+
249
+ const tooling = JSON.parse(await readFile(join(root, 'packages/tooling/package.json'), 'utf8'));
250
+ expect(tooling.version).toBeUndefined();
251
+ expect(tooling.nx).toEqual({ name: 'tooling' });
252
+ expect(validateNxProjectNames(root)).toBe(0);
253
+ expect(listValidCommitScopes(root)).toEqual(new Set(['release', 'tooling']));
254
+ } finally {
255
+ await rm(root, { recursive: true, force: true });
256
+ }
257
+ });
219
258
  });
220
259
 
221
260
  describe('workspace package script policy', () => {
@@ -428,11 +467,146 @@ describe('workspace package script policy', () => {
428
467
  }
429
468
  });
430
469
 
431
- it('rejects old test tsconfig output and build.zig without steps', async () => {
470
+ it('accepts wildcard aggregate build dependencies', async () => {
471
+ const root = await createWorkspace({
472
+ rootName: '@smoothbricks/codebase',
473
+ packages: [
474
+ {
475
+ dir: 'native',
476
+ name: '@smoothbricks/native',
477
+ nx: {
478
+ name: 'native',
479
+ targets: {
480
+ build: { executor: 'nx:noop', dependsOn: buildOutputDependencies },
481
+ },
482
+ },
483
+ },
484
+ ],
485
+ });
486
+ try {
487
+ expect(validateWorkspaceDependencies(root)).toBe(0);
488
+ } finally {
489
+ await rm(root, { recursive: true, force: true });
490
+ }
491
+ });
492
+
493
+ it('removes noop aggregate build targets when wildcard dependencies match resolved Nx plugin output', async () => {
494
+ const root = await createWorkspace({
495
+ rootName: '@smoothbricks/codebase',
496
+ packages: [
497
+ {
498
+ dir: 'native',
499
+ name: '@smoothbricks/native',
500
+ nx: {
501
+ name: 'native',
502
+ targets: {
503
+ build: { executor: 'nx:noop', dependsOn: buildOutputDependencies },
504
+ },
505
+ },
506
+ },
507
+ ],
508
+ });
509
+ try {
510
+ const resolvedTargetsByProject = new Map([
511
+ ['native', { targets: new Set(['build', 'tsc-js', 'zig-wasm']), buildDependsOn: buildOutputDependencies }],
512
+ ]);
513
+
514
+ applyWorkspaceDependencyDefaults(root, { resolvedTargetsByProject });
515
+
516
+ const native = await readJson(join(root, 'packages/native/package.json'));
517
+ expect(native.nx).toEqual({ name: 'native', targets: {} });
518
+ } finally {
519
+ await rm(root, { recursive: true, force: true });
520
+ }
521
+ });
522
+
523
+ it('preserves explicit repo-local output targets with cache metadata', async () => {
524
+ const root = await createWorkspace({
525
+ rootName: '@smoothbricks/codebase',
526
+ packages: [
527
+ {
528
+ dir: 'bundled',
529
+ name: '@smoothbricks/bundled',
530
+ nx: {
531
+ name: 'bundled',
532
+ targets: {
533
+ build: { executor: 'nx:noop', dependsOn: buildOutputDependencies },
534
+ 'tsdown-js': {
535
+ executor: 'nx:run-commands',
536
+ cache: true,
537
+ inputs: ['production', '^production'],
538
+ outputs: ['{projectRoot}/dist'],
539
+ options: { command: 'tsdown', cwd: '{projectRoot}' },
540
+ },
541
+ },
542
+ },
543
+ },
544
+ ],
545
+ });
546
+ try {
547
+ expect(validateWorkspaceDependencies(root)).toBe(0);
548
+
549
+ applyWorkspaceDependencyDefaults(root);
550
+
551
+ const bundled = await readJson(join(root, 'packages/bundled/package.json'));
552
+ expect(bundled).toMatchObject({
553
+ nx: {
554
+ targets: {
555
+ build: { dependsOn: buildOutputDependencies },
556
+ 'tsdown-js': {
557
+ executor: 'nx:run-commands',
558
+ cache: true,
559
+ inputs: ['production', '^production'],
560
+ outputs: ['{projectRoot}/dist'],
561
+ options: { command: 'tsdown', cwd: '{projectRoot}' },
562
+ },
563
+ },
564
+ },
565
+ });
566
+ } finally {
567
+ await rm(root, { recursive: true, force: true });
568
+ }
569
+ });
570
+
571
+ it('rejects build.zig without steps', async () => {
432
572
  const root = await createWorkspace({
433
573
  rootName: '@smoothbricks/codebase',
434
574
  packages: [{ dir: 'native', name: '@smoothbricks/native', nx: { name: 'native' } }],
435
575
  });
576
+ try {
577
+ await writeFile(join(root, 'packages/native/build.zig'), 'pub fn build(b: *std.Build) void { _ = b; }\n');
578
+
579
+ expect(validateWorkspaceDependencies(root)).toBe(1);
580
+ } finally {
581
+ await rm(root, { recursive: true, force: true });
582
+ }
583
+ });
584
+
585
+ it('rejects old test tsconfig output for packages with test runners', async () => {
586
+ const root = await createWorkspace({
587
+ rootName: '@smoothbricks/codebase',
588
+ packages: [
589
+ {
590
+ dir: 'native',
591
+ name: '@smoothbricks/native',
592
+ nx: {
593
+ name: 'native',
594
+ targets: {
595
+ test: {
596
+ executor: BOUNDED_TEST_EXECUTOR,
597
+ options: {
598
+ command: 'bun test',
599
+ cwd: '{projectRoot}',
600
+ timeoutMs: BOUNDED_TEST_TIMEOUT_MS,
601
+ killAfterMs: BOUNDED_TEST_KILL_AFTER_MS,
602
+ },
603
+ },
604
+ },
605
+ },
606
+ scripts: { test: 'nx run native:test --outputStyle=stream' },
607
+ },
608
+ ],
609
+ });
436
610
  try {
437
611
  await writeJson(join(root, 'packages/native/tsconfig.test.json'), {
438
612
  compilerOptions: {
@@ -443,9 +617,8 @@ describe('workspace package script policy', () => {
443
617
  tsBuildInfoFile: 'dist-test/tsconfig.test.tsbuildinfo',
444
618
  },
445
619
  });
446
- await writeFile(join(root, 'packages/native/build.zig'), 'pub fn build(b: *std.Build) void { _ = b; }\n');
447
620
 
448
- expect(validateWorkspaceDependencies(root)).toBe(7);
621
+ expect(validateWorkspaceDependencies(root)).toBe(6);
449
622
  } finally {
450
623
  await rm(root, { recursive: true, force: true });
451
624
  }
@@ -467,7 +640,7 @@ describe('workspace package script policy', () => {
467
640
  compilerOptions: { baseUrl: '.', rootDir: 'src', types: ['node'], outDir: 'dist' },
468
641
  });
469
642
 
470
- expect(validateWorkspaceDependencies(root)).toBe(1);
643
+ expect(validateWorkspaceDependencies(root)).toBe(2);
471
644
 
472
645
  applyWorkspaceDependencyDefaults(root);
473
646
 
@@ -500,18 +673,42 @@ describe('workspace package script policy', () => {
500
673
  }
501
674
  });
502
675
 
503
- it('does not require tsconfig.test.json for non-Bun test runners', async () => {
676
+ it('creates no-emit test typecheck config for packages that use vitest', async () => {
504
677
  const root = await createWorkspace({
505
678
  rootName: '@smoothbricks/codebase',
506
679
  packages: [{ dir: 'app', name: '@smoothbricks/app', scripts: { test: 'vitest run' } }],
507
680
  });
508
681
  try {
682
+ await writeJson(join(root, 'packages/app/tsconfig.lib.json'), {
683
+ extends: '../../tsconfig.base.json',
684
+ compilerOptions: { baseUrl: '.', rootDir: 'src', outDir: 'dist' },
685
+ });
686
+
687
+ expect(validateWorkspaceDependencies(root)).toBe(2);
688
+
689
+ applyWorkspaceDependencyDefaults(root);
690
+
691
+ const testTsconfig = await readJson(join(root, 'packages/app/tsconfig.test.json'));
692
+ expect(testTsconfig).toMatchObject({ compilerOptions: { noEmit: true } });
693
+ expect(testTsconfig).not.toMatchObject({ compilerOptions: { types: expect.anything() } });
509
694
  expect(validateWorkspaceDependencies(root)).toBe(0);
510
695
  } finally {
511
696
  await rm(root, { recursive: true, force: true });
512
697
  }
513
698
  });
514
699
 
700
+ it('does not require tsconfig.test.json for other test runners but still requires bounded execution', async () => {
701
+ const root = await createWorkspace({
702
+ rootName: '@smoothbricks/codebase',
703
+ packages: [{ dir: 'app', name: '@smoothbricks/app', scripts: { test: 'node --test' } }],
704
+ });
705
+ try {
706
+ expect(validateWorkspaceDependencies(root)).toBe(1);
707
+ } finally {
708
+ await rm(root, { recursive: true, force: true });
709
+ }
710
+ });
711
+
515
712
  it('detects bun test commands configured directly in Nx targets', async () => {
516
713
  const root = await createWorkspace({
517
714
  rootName: '@smoothbricks/codebase',
@@ -529,7 +726,7 @@ describe('workspace package script policy', () => {
529
726
  ],
530
727
  });
531
728
  try {
532
- expect(validateWorkspaceDependencies(root)).toBe(1);
729
+ expect(validateWorkspaceDependencies(root)).toBe(2);
533
730
 
534
731
  applyWorkspaceDependencyDefaults(root);
535
732
 
@@ -559,6 +756,21 @@ describe('workspace package script policy', () => {
559
756
  }
560
757
  });
561
758
 
759
+ it('requires a test script or target when test files exist', async () => {
760
+ const root = await createWorkspace({
761
+ rootName: '@smoothbricks/codebase',
762
+ packages: [{ dir: 'app', name: '@smoothbricks/app' }],
763
+ });
764
+ try {
765
+ await mkdir(join(root, 'packages/app/src'), { recursive: true });
766
+ await writeFile(join(root, 'packages/app/src/example.test.ts'), 'export const testValue = true;\n');
767
+
768
+ expect(validateWorkspaceDependencies(root)).toBe(1);
769
+ } finally {
770
+ await rm(root, { recursive: true, force: true });
771
+ }
772
+ });
773
+
562
774
  it('rewrites safe scripts that use workspace dependencies into Nx aliases', async () => {
563
775
  const root = await createWorkspace({
564
776
  rootName: '@smoothbricks/codebase',
@@ -581,16 +793,16 @@ describe('workspace package script policy', () => {
581
793
  ],
582
794
  });
583
795
  try {
584
- expect(validateWorkspaceDependencies(root)).toBe(6);
796
+ expect(validateWorkspaceDependencies(root)).toBe(7);
585
797
 
586
798
  applyWorkspaceDependencyDefaults(root);
587
799
 
588
800
  const app = await readJson(join(root, 'packages/app/package.json'));
589
801
  expect(app.dependencies).toEqual({ '@smoothbricks/lib': 'workspace:*' });
590
802
  expect(app.scripts).toEqual({
591
- dev: 'nx run app:dev --tui=false --outputStyle=stream',
592
- serve: 'nx run app:serve --tui=false --outputStyle=stream',
593
- test: 'nx run app:test',
803
+ dev: 'nx run app:dev --outputStyle=dynamic-legacy',
804
+ serve: 'nx run app:serve --outputStyle=dynamic-legacy',
805
+ test: 'nx run app:test --outputStyle=stream',
594
806
  build: 'nx run app:build',
595
807
  deploy: 'wrangler deploy',
596
808
  astro: 'astro',
@@ -611,9 +823,13 @@ describe('workspace package script policy', () => {
611
823
  options: { command: 'vite dev --host 0.0.0.0', cwd: '{projectRoot}' },
612
824
  },
613
825
  test: {
614
- executor: 'nx:run-commands',
615
- dependsOn: ['^build'],
616
- options: { command: 'bun test --pass-with-no-tests', cwd: '{projectRoot}' },
826
+ executor: BOUNDED_TEST_EXECUTOR,
827
+ options: {
828
+ command: 'bun test --pass-with-no-tests',
829
+ cwd: '{projectRoot}',
830
+ timeoutMs: BOUNDED_TEST_TIMEOUT_MS,
831
+ killAfterMs: BOUNDED_TEST_KILL_AFTER_MS,
832
+ },
617
833
  },
618
834
  build: {
619
835
  executor: 'nx:run-commands',
@@ -637,13 +853,18 @@ describe('workspace package script policy', () => {
637
853
  dir: 'app',
638
854
  name: '@smoothbricks/app',
639
855
  dependencies: { '@smoothbricks/lib': 'workspace:*' },
640
- scripts: { test: 'nx run app:test' },
856
+ scripts: { test: 'nx run app:test --outputStyle=stream' },
641
857
  nx: {
642
858
  name: 'app',
643
859
  targets: {
644
860
  test: {
645
- executor: 'nx:run-commands',
646
- options: { command: 'bun run test', cwd: '{projectRoot}' },
861
+ executor: BOUNDED_TEST_EXECUTOR,
862
+ options: {
863
+ command: 'bun run test',
864
+ cwd: '{projectRoot}',
865
+ timeoutMs: BOUNDED_TEST_TIMEOUT_MS,
866
+ killAfterMs: BOUNDED_TEST_KILL_AFTER_MS,
867
+ },
647
868
  },
648
869
  },
649
870
  },
@@ -651,7 +872,7 @@ describe('workspace package script policy', () => {
651
872
  ],
652
873
  });
653
874
  try {
654
- expect(validateWorkspaceDependencies(root)).toBe(2);
875
+ expect(validateWorkspaceDependencies(root)).toBe(1);
655
876
  } finally {
656
877
  await rm(root, { recursive: true, force: true });
657
878
  }
@@ -681,9 +902,9 @@ describe('workspace package script policy', () => {
681
902
 
682
903
  const website = await readJson(join(root, 'packages/website/package.json'));
683
904
  expect(website.scripts).toEqual({
684
- dev: 'nx run website:dev --tui=false --outputStyle=stream',
905
+ dev: 'nx run website:dev --outputStyle=dynamic-legacy',
685
906
  build: 'nx run website:build',
686
- preview: 'nx run website:preview --tui=false --outputStyle=stream',
907
+ preview: 'nx run website:preview --outputStyle=stream',
687
908
  astro: 'astro',
688
909
  });
689
910
  expect(website.nx).toEqual({
@@ -737,19 +958,23 @@ describe('workspace package script policy', () => {
737
958
  ],
738
959
  });
739
960
  try {
740
- expect(validateWorkspaceDependencies(root)).toBe(2);
961
+ expect(validateWorkspaceDependencies(root)).toBe(3);
741
962
 
742
963
  applyWorkspaceDependencyDefaults(root);
743
964
 
744
965
  const app = await readJson(join(root, 'packages/app/package.json'));
745
- expect(app.scripts).toEqual({ test: 'nx run @external/app:test' });
966
+ expect(app.scripts).toEqual({ test: 'nx run @external/app:test --outputStyle=stream' });
746
967
  expect(app.nx).toEqual({
747
968
  name: '@external/app',
748
969
  targets: {
749
970
  test: {
750
- executor: 'nx:run-commands',
751
- dependsOn: ['^build'],
752
- options: { command: 'bun test --pass-with-no-tests', cwd: '{projectRoot}' },
971
+ executor: BOUNDED_TEST_EXECUTOR,
972
+ options: {
973
+ command: 'bun test --pass-with-no-tests',
974
+ cwd: '{projectRoot}',
975
+ timeoutMs: BOUNDED_TEST_TIMEOUT_MS,
976
+ killAfterMs: BOUNDED_TEST_KILL_AFTER_MS,
977
+ },
753
978
  },
754
979
  },
755
980
  });
@@ -765,6 +990,8 @@ async function createWorkspace(input: {
765
990
  packages: Array<{
766
991
  dir: string;
767
992
  name: string;
993
+ private?: boolean;
994
+ version?: string | null;
768
995
  dependencies?: Record<string, string>;
769
996
  scripts?: Record<string, string>;
770
997
  nx?: Record<string, unknown>;
@@ -780,7 +1007,8 @@ async function createWorkspace(input: {
780
1007
  for (const pkg of input.packages) {
781
1008
  await writeJson(join(root, `packages/${pkg.dir}/package.json`), {
782
1009
  name: pkg.name,
783
- version: '0.0.0',
1010
+ ...(pkg.version === null ? {} : { version: pkg.version ?? '0.0.0' }),
1011
+ ...(pkg.private === undefined ? {} : { private: pkg.private }),
784
1012
  ...(pkg.dependencies ? { dependencies: pkg.dependencies } : {}),
785
1013
  ...(pkg.scripts ? { scripts: pkg.scripts } : {}),
786
1014
  ...(pkg.nx ? { nx: pkg.nx } : {}),
@@ -844,7 +1072,7 @@ function validNxJson(): Record<string, unknown> {
844
1072
  specifierSource: 'conventional-commits',
845
1073
  currentVersionResolver: 'git-tag',
846
1074
  fallbackCurrentVersionResolver: 'disk',
847
- versionActions: '@smoothbricks/cli/nx-version-actions',
1075
+ versionActions: '@smoothbricks/nx-plugin/version-actions',
848
1076
  },
849
1077
  releaseTag: { pattern: '{projectName}@{version}' },
850
1078
  changelog: {