@blotch/dolan 22.2.2 → 22.2.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.
Files changed (154) hide show
  1. package/dist/commands/add.d.ts +6 -2
  2. package/dist/commands/add.js +32 -10
  3. package/dist/commands/add.js.map +1 -1
  4. package/dist/commands/add.test.js +129 -9
  5. package/dist/commands/add.test.js.map +1 -1
  6. package/dist/commands/diff.d.ts +2 -1
  7. package/dist/commands/diff.js +38 -25
  8. package/dist/commands/diff.js.map +1 -1
  9. package/dist/commands/diff.test.js +72 -11
  10. package/dist/commands/diff.test.js.map +1 -1
  11. package/dist/commands/doctor.js +41 -27
  12. package/dist/commands/doctor.js.map +1 -1
  13. package/dist/commands/doctor.test.js +93 -9
  14. package/dist/commands/doctor.test.js.map +1 -1
  15. package/dist/commands/eject.js +7 -0
  16. package/dist/commands/eject.js.map +1 -1
  17. package/dist/commands/eject.test.js +39 -0
  18. package/dist/commands/eject.test.js.map +1 -1
  19. package/dist/commands/info.js +3 -2
  20. package/dist/commands/info.js.map +1 -1
  21. package/dist/commands/init.d.ts +37 -1
  22. package/dist/commands/init.js +149 -40
  23. package/dist/commands/init.js.map +1 -1
  24. package/dist/commands/init.test.js +285 -26
  25. package/dist/commands/init.test.js.map +1 -1
  26. package/dist/commands/install-packages.d.ts +6 -0
  27. package/dist/commands/install-packages.js +20 -0
  28. package/dist/commands/install-packages.js.map +1 -0
  29. package/dist/commands/install-packages.test.d.ts +1 -0
  30. package/dist/commands/install-packages.test.js +55 -0
  31. package/dist/commands/install-packages.test.js.map +1 -0
  32. package/dist/commands/list.js +21 -14
  33. package/dist/commands/list.js.map +1 -1
  34. package/dist/commands/list.test.js +62 -5
  35. package/dist/commands/list.test.js.map +1 -1
  36. package/dist/commands/outdated.js +11 -10
  37. package/dist/commands/outdated.js.map +1 -1
  38. package/dist/commands/outdated.test.js +20 -5
  39. package/dist/commands/outdated.test.js.map +1 -1
  40. package/dist/commands/remove.d.ts +4 -0
  41. package/dist/commands/remove.js +17 -4
  42. package/dist/commands/remove.js.map +1 -1
  43. package/dist/commands/remove.test.js +53 -2
  44. package/dist/commands/remove.test.js.map +1 -1
  45. package/dist/commands/update.d.ts +6 -0
  46. package/dist/commands/update.js +59 -24
  47. package/dist/commands/update.js.map +1 -1
  48. package/dist/commands/update.test.js +273 -31
  49. package/dist/commands/update.test.js.map +1 -1
  50. package/dist/index.js +7 -0
  51. package/dist/index.js.map +1 -1
  52. package/dist/registry.d.ts +8 -0
  53. package/dist/registry.js +32 -24
  54. package/dist/registry.js.map +1 -1
  55. package/dist/registry.test.js +76 -37
  56. package/dist/registry.test.js.map +1 -1
  57. package/dist/testing/set-input-signal.d.ts +13 -0
  58. package/dist/testing/set-input-signal.js +20 -0
  59. package/dist/testing/set-input-signal.js.map +1 -0
  60. package/dist/testing/set-input-signal.test.d.ts +1 -0
  61. package/dist/testing/set-input-signal.test.js +20 -0
  62. package/dist/testing/set-input-signal.test.js.map +1 -0
  63. package/dist/ui/components/action-sheet/action-sheet.test.ts +48 -1
  64. package/dist/ui/components/action-sheet/action-sheet.ts +37 -9
  65. package/dist/ui/components/alert-dialog/alert-dialog.test.ts +49 -2
  66. package/dist/ui/components/alert-dialog/alert-dialog.ts +43 -12
  67. package/dist/ui/components/area-chart/area-chart-render.test.ts +52 -110
  68. package/dist/ui/components/area-chart/area-chart.test.ts +58 -123
  69. package/dist/ui/components/area-chart/area-chart.ts +100 -315
  70. package/dist/ui/components/area-chart/index.ts +2 -3
  71. package/dist/ui/components/bar-chart/bar-chart-render.test.ts +47 -163
  72. package/dist/ui/components/bar-chart/bar-chart.test.ts +83 -37
  73. package/dist/ui/components/bar-chart/bar-chart.ts +140 -294
  74. package/dist/ui/components/bar-chart/index.ts +3 -3
  75. package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +191 -9
  76. package/dist/ui/components/bottom-sheet/bottom-sheet.ts +136 -20
  77. package/dist/ui/components/button/button-render.test.ts +19 -0
  78. package/dist/ui/components/button/button.ts +18 -2
  79. package/dist/ui/components/candlestick-chart/candlestick-chart-render.test.ts +48 -163
  80. package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +106 -62
  81. package/dist/ui/components/candlestick-chart/candlestick-chart.ts +150 -323
  82. package/dist/ui/components/candlestick-chart/index.ts +3 -4
  83. package/dist/ui/components/chart/chart-focus.test.ts +132 -0
  84. package/dist/ui/components/chart/chart-focus.ts +115 -0
  85. package/dist/ui/components/chart/chart-presets.test.ts +139 -0
  86. package/dist/ui/components/chart/chart-presets.ts +271 -0
  87. package/dist/ui/components/chart/chart-render.test.ts +730 -0
  88. package/dist/ui/components/chart/chart-scene.test.ts +589 -0
  89. package/dist/ui/components/chart/chart-scene.ts +483 -0
  90. package/dist/ui/components/chart/chart-theme.test.ts +85 -0
  91. package/dist/ui/components/chart/chart-theme.ts +198 -0
  92. package/dist/ui/components/chart/chart-zoom.test.ts +159 -0
  93. package/dist/ui/components/chart/chart-zoom.ts +162 -0
  94. package/dist/ui/components/chart/chart.ts +921 -0
  95. package/dist/ui/components/chart/index.ts +24 -0
  96. package/dist/ui/components/dialog/dialog.test.ts +49 -2
  97. package/dist/ui/components/dialog/dialog.ts +43 -12
  98. package/dist/ui/components/icon/icon-theme.test.ts +133 -0
  99. package/dist/ui/components/icon/icon-theme.ts +204 -0
  100. package/dist/ui/components/icon/icon.test.ts +51 -2
  101. package/dist/ui/components/icon/icon.ts +38 -21
  102. package/dist/ui/components/line-chart/index.ts +2 -3
  103. package/dist/ui/components/line-chart/line-chart-render.test.ts +69 -107
  104. package/dist/ui/components/line-chart/line-chart.test.ts +63 -45
  105. package/dist/ui/components/line-chart/line-chart.ts +83 -241
  106. package/dist/ui/components/list/list.test.ts +69 -2
  107. package/dist/ui/components/list/list.ts +21 -15
  108. package/dist/ui/components/nav-drawer/nav-drawer.test.ts +80 -1
  109. package/dist/ui/components/nav-drawer/nav-drawer.ts +66 -11
  110. package/dist/ui/components/pagination/pagination.test.ts +62 -0
  111. package/dist/ui/components/pagination/pagination.ts +55 -8
  112. package/dist/ui/components/scatter-chart/index.ts +2 -3
  113. package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +51 -144
  114. package/dist/ui/components/scatter-chart/scatter-chart.test.ts +57 -25
  115. package/dist/ui/components/scatter-chart/scatter-chart.ts +104 -250
  116. package/dist/ui/components/scroll-area/scroll-area.test.ts +37 -2
  117. package/dist/ui/components/scroll-area/scroll-area.ts +8 -8
  118. package/dist/ui/components/spinner/spinner.test.ts +27 -0
  119. package/dist/ui/components/spinner/spinner.ts +33 -23
  120. package/dist/ui/components/toast/toast.test.ts +37 -0
  121. package/dist/ui/components/toast/toast.ts +20 -4
  122. package/dist/ui/theme/chart-colors.test.ts +30 -0
  123. package/dist/ui/theme/dark.css +8 -0
  124. package/dist/ui/theme/default.css +8 -0
  125. package/dist/ui/theme/icon-colors.test.ts +30 -0
  126. package/dist/ui/theme/tailwind-plugin.ts +10 -1
  127. package/dist/utils/installed.d.ts +17 -0
  128. package/dist/utils/installed.js +27 -0
  129. package/dist/utils/installed.js.map +1 -0
  130. package/dist/utils/installed.test.d.ts +1 -0
  131. package/dist/utils/installed.test.js +41 -0
  132. package/dist/utils/installed.test.js.map +1 -0
  133. package/dist/utils/interactive.d.ts +9 -0
  134. package/dist/utils/interactive.js +10 -0
  135. package/dist/utils/interactive.js.map +1 -0
  136. package/dist/utils/interactive.test.d.ts +1 -0
  137. package/dist/utils/interactive.test.js +24 -0
  138. package/dist/utils/interactive.test.js.map +1 -0
  139. package/dist/utils/packages.d.ts +36 -0
  140. package/dist/utils/packages.js +70 -0
  141. package/dist/utils/packages.js.map +1 -0
  142. package/dist/utils/packages.test.d.ts +1 -0
  143. package/dist/utils/packages.test.js +89 -0
  144. package/dist/utils/packages.test.js.map +1 -0
  145. package/dist/utils/resolve-paths.d.ts +17 -1
  146. package/dist/utils/resolve-paths.js +23 -3
  147. package/dist/utils/resolve-paths.js.map +1 -1
  148. package/dist/utils/resolve-paths.test.js +34 -2
  149. package/dist/utils/resolve-paths.test.js.map +1 -1
  150. package/package.json +5 -1
  151. package/dist/ui/components/cartesian-chart/cartesian-chart-render.test.ts +0 -188
  152. package/dist/ui/components/cartesian-chart/cartesian-chart.test.ts +0 -525
  153. package/dist/ui/components/cartesian-chart/cartesian-chart.ts +0 -1140
  154. package/dist/ui/components/cartesian-chart/index.ts +0 -18
@@ -20,6 +20,12 @@ vi.mock('@clack/prompts', () => ({
20
20
  cancel: vi.fn(),
21
21
  isCancel: vi.fn().mockReturnValue(false),
22
22
  }));
23
+ // Vitest's stdin isn't a terminal, which reads as "nobody can answer". Most
24
+ // tests are about the questions, so they run as if in one.
25
+ let interactive = true;
26
+ vi.mock('../utils/interactive.js', () => ({
27
+ isInteractive: () => interactive,
28
+ }));
23
29
  let fixture;
24
30
  vi.mock('../utils/resolve-paths.js', () => ({
25
31
  getUiSourceDir: () => fixture.uiDir,
@@ -27,7 +33,16 @@ vi.mock('../utils/resolve-paths.js', () => ({
27
33
  getComponentFiles: (name) => readdirSync(join(fixture.uiDir, 'components', name)).filter((f) => f.endsWith('.ts')),
28
34
  rewriteImports: (content) => content,
29
35
  }));
36
+ // The install step runs the project's package manager; record calls instead.
37
+ const installComponentPackages = vi.fn();
38
+ vi.mock('./install-packages.js', () => ({
39
+ installComponentPackages: (...args) => installComponentPackages(...args),
40
+ }));
30
41
  beforeEach(() => {
42
+ interactive = true;
43
+ // Each test sees only its own calls. Without this, an assertion could pass
44
+ // on a message an earlier test logged.
45
+ vi.clearAllMocks();
31
46
  vi.spyOn(process, 'exit').mockImplementation((code) => {
32
47
  throw new Error(`process.exit(${code})`);
33
48
  });
@@ -91,28 +106,28 @@ describe('updateCommand --selective', () => {
91
106
  it('handles mixed decisions across multiple files', async () => {
92
107
  const cardOriginal = 'export const Card = { v: 1 };';
93
108
  const cardUpstream = 'export const Card = { v: 2 };';
94
- const spinnerOriginal = 'export const Spinner = { v: 1 };';
95
- const spinnerUpstream = 'export const Spinner = { v: 2 };';
109
+ const skeletonOriginal = 'export const Skeleton = { v: 1 };';
110
+ const skeletonUpstream = 'export const Skeleton = { v: 2 };';
96
111
  const cardHash = hashContent(cardOriginal);
97
- const spinnerHash = hashContent(spinnerOriginal);
112
+ const skeletonHash = hashContent(skeletonOriginal);
98
113
  fixture = createFixture({
99
114
  config: DEFAULT_CONFIG,
100
115
  lockfile: {
101
116
  version: 1,
102
117
  components: {
103
118
  card: { 'card.ts': { hash: cardHash } },
104
- spinner: { 'spinner.ts': { hash: spinnerHash } },
119
+ skeleton: { 'skeleton.ts': { hash: skeletonHash } },
105
120
  },
106
121
  utils: {},
107
122
  theme: {},
108
123
  },
109
124
  components: {
110
125
  card: { 'card.ts': cardOriginal },
111
- spinner: { 'spinner.ts': spinnerOriginal },
126
+ skeleton: { 'skeleton.ts': skeletonOriginal },
112
127
  },
113
128
  uiSource: {
114
129
  card: { 'card.ts': cardUpstream },
115
- spinner: { 'spinner.ts': spinnerUpstream },
130
+ skeleton: { 'skeleton.ts': skeletonUpstream },
116
131
  },
117
132
  });
118
133
  vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
@@ -124,11 +139,11 @@ describe('updateCommand --selective', () => {
124
139
  await updateCommand({ selective: true });
125
140
  // Card should be updated (first select → upstream)
126
141
  expect(readInstalledFile(fixture.dir, 'card', 'card.ts')).toBe(cardUpstream);
127
- // Spinner should be skipped (second select → keep)
128
- expect(readInstalledFile(fixture.dir, 'spinner', 'spinner.ts')).toBe(spinnerOriginal);
142
+ // Skeleton should be skipped (second select → keep)
143
+ expect(readInstalledFile(fixture.dir, 'skeleton', 'skeleton.ts')).toBe(skeletonOriginal);
129
144
  const lockfile = readLockfile(fixture.dir);
130
145
  expect(lockfile.components.card['card.ts'].hash).toBe(hashContent(cardUpstream));
131
- expect(lockfile.components.spinner['spinner.ts'].hash).toBe(spinnerHash);
146
+ expect(lockfile.components.skeleton['skeleton.ts'].hash).toBe(skeletonHash);
132
147
  });
133
148
  it('does not create new-upstream file when user chooses skip', async () => {
134
149
  const upstream = 'export const Card = {};';
@@ -136,7 +151,7 @@ describe('updateCommand --selective', () => {
136
151
  config: DEFAULT_CONFIG,
137
152
  lockfile: {
138
153
  version: 1,
139
- components: {},
154
+ components: { card: {} },
140
155
  utils: {},
141
156
  theme: {},
142
157
  },
@@ -169,7 +184,6 @@ describe('updateCommand --selective', () => {
169
184
  });
170
185
  vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
171
186
  const p = await import('@clack/prompts');
172
- p.select.mockClear();
173
187
  const { updateCommand } = await import('./update');
174
188
  await updateCommand({ force: true, selective: true });
175
189
  expect(readInstalledFile(fixture.dir, 'card', 'card.ts')).toBe(upstream);
@@ -219,7 +233,6 @@ describe('updateCommand --selective', () => {
219
233
  vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
220
234
  const p = await import('@clack/prompts');
221
235
  p.confirm.mockResolvedValue(true);
222
- p.select.mockClear();
223
236
  const { updateCommand } = await import('./update');
224
237
  await updateCommand({});
225
238
  expect(p.confirm).toHaveBeenCalled();
@@ -306,8 +319,6 @@ describe('updateCommand --force', () => {
306
319
  });
307
320
  vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
308
321
  const p = await import('@clack/prompts');
309
- p.select.mockClear();
310
- p.confirm.mockClear();
311
322
  const { updateCommand } = await import('./update');
312
323
  await updateCommand({ force: true });
313
324
  // Local edit discarded, file reset to upstream.
@@ -337,7 +348,6 @@ describe('updateCommand --force', () => {
337
348
  });
338
349
  vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
339
350
  const p = await import('@clack/prompts');
340
- p.select.mockClear();
341
351
  const { updateCommand } = await import('./update');
342
352
  await updateCommand({ force: true });
343
353
  expect(readInstalledFile(fixture.dir, 'card', 'card.ts')).toBe(upstream);
@@ -345,14 +355,14 @@ describe('updateCommand --force', () => {
345
355
  const lockfile = readLockfile(fixture.dir);
346
356
  expect(lockfile.components.card['card.ts'].hash).toBe(hashContent(upstream));
347
357
  });
348
- it('overwrites a drifted file with no lockfile entry (unknown base → conflict)', async () => {
358
+ it('overwrites a drifted file with no lockfile hash (unknown base → conflict)', async () => {
349
359
  // With no stored hash the base is unknown, so any local difference is
350
360
  // treated as a conflict. --force must still overwrite it.
351
361
  const userVersion = 'export const Card = { v: 1, local: true };';
352
362
  const upstream = 'export const Card = { v: 2 };';
353
363
  fixture = createFixture({
354
364
  config: DEFAULT_CONFIG,
355
- lockfile: { version: 1, components: {}, utils: {}, theme: {} },
365
+ lockfile: { version: 1, components: { card: {} }, utils: {}, theme: {} },
356
366
  components: { card: { 'card.ts': userVersion } },
357
367
  uiSource: { card: { 'card.ts': upstream } },
358
368
  });
@@ -477,25 +487,25 @@ describe('updateCommand --force', () => {
477
487
  version: 1,
478
488
  components: {
479
489
  card: { 'card.ts': { hash: hashContent('card-base') } },
480
- spinner: { 'spinner.ts': { hash: hashContent('spinner-base') } },
490
+ skeleton: { 'skeleton.ts': { hash: hashContent('skeleton-base') } },
481
491
  },
482
492
  utils: {},
483
493
  theme: {},
484
494
  },
485
495
  components: {
486
496
  card: { 'card.ts': 'card-local' },
487
- spinner: { 'spinner.ts': 'spinner-local' },
497
+ skeleton: { 'skeleton.ts': 'skeleton-local' },
488
498
  },
489
499
  uiSource: {
490
500
  card: { 'card.ts': 'card-upstream' },
491
- spinner: { 'spinner.ts': 'spinner-upstream' },
501
+ skeleton: { 'skeleton.ts': 'skeleton-upstream' },
492
502
  },
493
503
  });
494
504
  vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
495
505
  const { updateCommand } = await import('./update');
496
506
  await updateCommand({ force: true });
497
507
  expect(readInstalledFile(fixture.dir, 'card', 'card.ts')).toBe('card-upstream');
498
- expect(readInstalledFile(fixture.dir, 'spinner', 'spinner.ts')).toBe('spinner-upstream');
508
+ expect(readInstalledFile(fixture.dir, 'skeleton', 'skeleton.ts')).toBe('skeleton-upstream');
499
509
  });
500
510
  it('does not delete untracked local files sitting alongside components', async () => {
501
511
  // Force overwrites tracked files but must not touch files the registry
@@ -635,8 +645,6 @@ describe('updateCommand --force', () => {
635
645
  seedTheme(fixture, 'default.css', 'theme-local'); // theme conflict
636
646
  vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
637
647
  const p = await import('@clack/prompts');
638
- p.select.mockClear();
639
- p.confirm.mockClear();
640
648
  const { updateCommand } = await import('./update');
641
649
  await updateCommand({ force: true });
642
650
  expect(p.select).not.toHaveBeenCalled();
@@ -781,8 +789,6 @@ describe('updateCommand — config / no-op paths', () => {
781
789
  });
782
790
  vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
783
791
  const p = await import('@clack/prompts');
784
- p.confirm.mockClear();
785
- p.select.mockClear();
786
792
  const { updateCommand } = await import('./update');
787
793
  await updateCommand({});
788
794
  // Nothing prompts — a lone user-modified file with no force/selective is
@@ -874,7 +880,6 @@ describe('updateCommand — config / no-op paths', () => {
874
880
  vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
875
881
  const p = await import('@clack/prompts');
876
882
  p.confirm.mockResolvedValueOnce(true);
877
- p.select.mockClear();
878
883
  const { updateCommand } = await import('./update');
879
884
  await updateCommand({});
880
885
  expect(readInstalledFile(fixture.dir, 'card', 'card.ts')).toBe(cardUpstream);
@@ -888,15 +893,15 @@ describe('updateCommand — config / no-op paths', () => {
888
893
  // The already up-to-date theme file's hash is still recorded.
889
894
  expect(lockfile.theme['default.css'].hash).toBe(hashContent(themeContent));
890
895
  });
891
- it('advances the lockfile for an up-to-date component and theme file with no prior lockfile entries', async () => {
892
- // Neither the component nor the theme file has ANY lockfile record at
893
- // all (not just a missing hash) — buildLockfile() must create fresh
894
- // entries for both from scratch on the "nothing to do" fast path.
896
+ it('advances the lockfile for an up-to-date component with no file hashes and a theme file with no entry', async () => {
897
+ // The component is tracked without file hashes, and the theme file has
898
+ // no lockfile record at all — buildLockfile() must create fresh entries
899
+ // for both on the "nothing to do" fast path.
895
900
  const cardContent = 'export const Card = {};';
896
901
  const themeContent = ':root { --primary: rgba(1, 1, 1, 1); }';
897
902
  fixture = createFixture({
898
903
  config: DEFAULT_CONFIG,
899
- lockfile: { version: 1, components: {}, utils: {}, theme: {} },
904
+ lockfile: { version: 1, components: { card: {} }, utils: {}, theme: {} },
900
905
  components: { card: { 'card.ts': cardContent } },
901
906
  uiSource: { card: { 'card.ts': cardContent } },
902
907
  themeFiles: { 'default.css': themeContent },
@@ -1230,4 +1235,241 @@ describe('updateCommand — selective review of shared theme files', () => {
1230
1235
  expect(lockfile.theme['default.css'].hash).toBe(hashContent(upstream));
1231
1236
  });
1232
1237
  });
1238
+ describe('updateCommand dependencies', () => {
1239
+ it("adds a dependency a component's new version needs, and its packages", async () => {
1240
+ // line-chart used to stand alone; upstream it now builds on chart, which
1241
+ // the project doesn't have yet.
1242
+ const lineChart = "import { UiChart } from '../chart/chart';";
1243
+ fixture = createFixture({
1244
+ config: DEFAULT_CONFIG,
1245
+ lockfile: {
1246
+ version: 1,
1247
+ components: {
1248
+ 'line-chart': { 'line-chart.ts': { hash: hashContent('old') } },
1249
+ },
1250
+ utils: {},
1251
+ theme: {},
1252
+ },
1253
+ components: { 'line-chart': { 'line-chart.ts': 'old' } },
1254
+ uiSource: {
1255
+ 'line-chart': { 'line-chart.ts': lineChart },
1256
+ chart: { 'chart.ts': 'export const UiChart = {};' },
1257
+ },
1258
+ });
1259
+ vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
1260
+ const { updateCommand } = await import('./update');
1261
+ await updateCommand({ force: true });
1262
+ expect(readInstalledFile(fixture.dir, 'chart', 'chart.ts')).toBe('export const UiChart = {};');
1263
+ expect(readInstalledFile(fixture.dir, 'line-chart', 'line-chart.ts')).toBe(lineChart);
1264
+ expect(installComponentPackages).toHaveBeenCalledWith(fixture.dir, [
1265
+ 'chart',
1266
+ 'line-chart',
1267
+ ]);
1268
+ });
1269
+ it('leaves out a new dependency the user skips in selective mode', async () => {
1270
+ const lineChart = "import { UiChart } from '../chart/chart';";
1271
+ fixture = createFixture({
1272
+ config: DEFAULT_CONFIG,
1273
+ lockfile: {
1274
+ version: 1,
1275
+ components: {
1276
+ 'line-chart': { 'line-chart.ts': { hash: hashContent(lineChart) } },
1277
+ },
1278
+ utils: {},
1279
+ theme: {},
1280
+ },
1281
+ components: { 'line-chart': { 'line-chart.ts': lineChart } },
1282
+ uiSource: {
1283
+ 'line-chart': { 'line-chart.ts': lineChart },
1284
+ chart: { 'chart.ts': 'export const UiChart = {};' },
1285
+ },
1286
+ });
1287
+ vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
1288
+ const p = await import('@clack/prompts');
1289
+ p.select.mockResolvedValueOnce('keep');
1290
+ const { updateCommand } = await import('./update');
1291
+ await updateCommand({ selective: true });
1292
+ expect(existsSync(join(fixture.dir, DEFAULT_CONFIG.aliases.components, 'chart/chart.ts'))).toBe(false);
1293
+ expect(readLockfile(fixture.dir).components['chart']).toEqual({});
1294
+ });
1295
+ });
1296
+ describe('updateCommand — ejected components', () => {
1297
+ /**
1298
+ * `dolan eject card` deleted card's lockfile entry and kept its files,
1299
+ * which the app has since changed.
1300
+ */
1301
+ const ejectedCard = (lockfileComponents = {}) => createFixture({
1302
+ config: DEFAULT_CONFIG,
1303
+ lockfile: {
1304
+ version: 1,
1305
+ components: lockfileComponents,
1306
+ utils: {},
1307
+ theme: {},
1308
+ },
1309
+ components: {
1310
+ card: { 'card.ts': 'export const Card = { mine: true };' },
1311
+ },
1312
+ uiSource: { card: { 'card.ts': 'export const Card = { v: 2 };' } },
1313
+ });
1314
+ it("doesn't overwrite or track them again, even with --force", async () => {
1315
+ fixture = ejectedCard();
1316
+ vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
1317
+ const { updateCommand } = await import('./update');
1318
+ await updateCommand({ force: true });
1319
+ expect(readInstalledFile(fixture.dir, 'card', 'card.ts')).toBe('export const Card = { mine: true };');
1320
+ expect(readLockfile(fixture.dir).components).not.toHaveProperty('card');
1321
+ });
1322
+ it("doesn't report them as a conflict", async () => {
1323
+ fixture = ejectedCard();
1324
+ vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
1325
+ const p = await import('@clack/prompts');
1326
+ const { updateCommand } = await import('./update');
1327
+ await updateCommand({});
1328
+ const messages = p.log.message.mock.calls
1329
+ .map((c) => c[0])
1330
+ .join('\n');
1331
+ expect(messages).not.toContain('card');
1332
+ expect(p.select).not.toHaveBeenCalled();
1333
+ });
1334
+ it('leaves an ejected dependency of a tracked component alone', async () => {
1335
+ // select is tracked and depends on bottom-sheet, which was ejected.
1336
+ const select = 'export const Select = {};';
1337
+ fixture = createFixture({
1338
+ config: DEFAULT_CONFIG,
1339
+ lockfile: {
1340
+ version: 1,
1341
+ components: { select: { 'select.ts': { hash: hashContent(select) } } },
1342
+ utils: {},
1343
+ theme: {},
1344
+ },
1345
+ components: {
1346
+ select: { 'select.ts': select },
1347
+ 'bottom-sheet': {
1348
+ 'bottom-sheet.ts': 'export const BottomSheet = { mine: true };',
1349
+ },
1350
+ },
1351
+ uiSource: {
1352
+ select: { 'select.ts': select },
1353
+ 'bottom-sheet': { 'bottom-sheet.ts': 'export const BottomSheet = {};' },
1354
+ },
1355
+ });
1356
+ vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
1357
+ const { updateCommand } = await import('./update');
1358
+ await updateCommand({ force: true });
1359
+ expect(readInstalledFile(fixture.dir, 'bottom-sheet', 'bottom-sheet.ts')).toBe('export const BottomSheet = { mine: true };');
1360
+ expect(readLockfile(fixture.dir).components).not.toHaveProperty('bottom-sheet');
1361
+ });
1362
+ });
1363
+ // CI, agents and scripts: a question there would wait for input that never
1364
+ // comes.
1365
+ describe('updateCommand without a terminal', () => {
1366
+ const base = 'export const Card = { v: 1 };';
1367
+ const userVersion = 'export const Card = { v: 1, local: true };';
1368
+ const upstream = 'export const Card = { v: 2 };';
1369
+ const themeBase = ':root { --primary: rgba(1, 1, 1, 1); }';
1370
+ const themeMine = ':root { --primary: rgba(9, 9, 9, 1); }';
1371
+ const themeUpstream = ':root { --primary: rgba(2, 2, 2, 1); }';
1372
+ /**
1373
+ * `card` and `default.css` conflict (both sides changed), and `skeleton` is
1374
+ * a plain auto-update.
1375
+ */
1376
+ const setup = () => {
1377
+ fixture = createFixture({
1378
+ config: DEFAULT_CONFIG,
1379
+ lockfile: {
1380
+ version: 1,
1381
+ components: {
1382
+ card: { 'card.ts': { hash: hashContent(base) } },
1383
+ skeleton: { 'skeleton.ts': { hash: hashContent(base) } },
1384
+ },
1385
+ utils: {},
1386
+ theme: { 'default.css': { hash: hashContent(themeBase) } },
1387
+ },
1388
+ components: {
1389
+ card: { 'card.ts': userVersion },
1390
+ skeleton: { 'skeleton.ts': base },
1391
+ },
1392
+ uiSource: {
1393
+ card: { 'card.ts': upstream },
1394
+ skeleton: { 'skeleton.ts': upstream },
1395
+ },
1396
+ themeFiles: { 'default.css': themeUpstream },
1397
+ });
1398
+ writeFileSync(join(fixture.dir, DEFAULT_CONFIG.aliases.theme, 'default.css'), themeMine);
1399
+ vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
1400
+ };
1401
+ const readTheme = () => readFileSync(join(fixture.dir, DEFAULT_CONFIG.aliases.theme, 'default.css'), 'utf-8');
1402
+ beforeEach(() => {
1403
+ interactive = false;
1404
+ });
1405
+ it('applies auto-updates without asking', async () => {
1406
+ fixture = createFixture({
1407
+ config: DEFAULT_CONFIG,
1408
+ lockfile: {
1409
+ version: 1,
1410
+ components: { card: { 'card.ts': { hash: hashContent(base) } } },
1411
+ utils: {},
1412
+ theme: {},
1413
+ },
1414
+ components: { card: { 'card.ts': base } },
1415
+ uiSource: { card: { 'card.ts': upstream } },
1416
+ });
1417
+ vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
1418
+ const p = await import('@clack/prompts');
1419
+ const { updateCommand } = await import('./update');
1420
+ await updateCommand({});
1421
+ expect(p.confirm).not.toHaveBeenCalled();
1422
+ expect(readInstalledFile(fixture.dir, 'card', 'card.ts')).toBe(upstream);
1423
+ expect(p.outro).toHaveBeenCalledWith('Update complete.');
1424
+ });
1425
+ it('leaves conflicts as they are for a terminal to ask about, and exits 1', async () => {
1426
+ setup();
1427
+ const p = await import('@clack/prompts');
1428
+ const { updateCommand } = await import('./update');
1429
+ await expect(updateCommand({})).rejects.toThrow('process.exit(1)');
1430
+ expect(p.select).not.toHaveBeenCalled();
1431
+ // The count is bold, and CI turns colors on, so match the plain text.
1432
+ const [warning] = p.log.warn.mock.calls[0];
1433
+ // oxlint-disable-next-line no-control-regex -- matching ANSI color codes
1434
+ expect(warning.replace(/\u001b\[\d+m/g, '')).toMatch(/2 conflict\(s\) left.*dolan update --force/);
1435
+ // The auto-update still applies.
1436
+ expect(readInstalledFile(fixture.dir, 'skeleton', 'skeleton.ts')).toBe(upstream);
1437
+ // The conflicts keep both their files and their lockfile base. Keeping
1438
+ // the user's version would advance the base and lose the upstream change.
1439
+ expect(readInstalledFile(fixture.dir, 'card', 'card.ts')).toBe(userVersion);
1440
+ expect(readTheme()).toBe(themeMine);
1441
+ const lockfile = readLockfile(fixture.dir);
1442
+ expect(lockfile.components.card['card.ts'].hash).toBe(hashContent(base));
1443
+ expect(lockfile.theme['default.css'].hash).toBe(hashContent(themeBase));
1444
+ // So an update in a terminal still asks about both.
1445
+ interactive = true;
1446
+ vi.clearAllMocks();
1447
+ // A reset drops answers an earlier test queued and never used.
1448
+ p.select
1449
+ .mockReset()
1450
+ .mockResolvedValue('upstream');
1451
+ await updateCommand({});
1452
+ expect(p.select).toHaveBeenCalledTimes(2);
1453
+ expect(readInstalledFile(fixture.dir, 'card', 'card.ts')).toBe(upstream);
1454
+ expect(readTheme()).toBe(themeUpstream);
1455
+ });
1456
+ it("takes upstream's version of conflicts with --force, which wins over --selective", async () => {
1457
+ setup();
1458
+ const p = await import('@clack/prompts');
1459
+ const { updateCommand } = await import('./update');
1460
+ await updateCommand({ force: true, selective: true });
1461
+ expect(p.log.error).not.toHaveBeenCalled();
1462
+ expect(readInstalledFile(fixture.dir, 'card', 'card.ts')).toBe(upstream);
1463
+ expect(readTheme()).toBe(themeUpstream);
1464
+ expect(p.outro).toHaveBeenCalledWith('Update complete.');
1465
+ });
1466
+ it('fails with --selective, which asks about each file', async () => {
1467
+ setup();
1468
+ const p = await import('@clack/prompts');
1469
+ const { updateCommand } = await import('./update');
1470
+ await expect(updateCommand({ selective: true })).rejects.toThrow('process.exit(1)');
1471
+ expect(p.log.error).toHaveBeenCalledWith(expect.stringContaining('--selective'));
1472
+ expect(readInstalledFile(fixture.dir, 'skeleton', 'skeleton.ts')).toBe(base);
1473
+ });
1474
+ });
1233
1475
  //# sourceMappingURL=update.test.js.map