@blotch/dolan 22.2.1 → 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.
- package/dist/commands/add.d.ts +6 -2
- package/dist/commands/add.js +32 -10
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/add.test.js +129 -9
- package/dist/commands/add.test.js.map +1 -1
- package/dist/commands/diff.d.ts +2 -1
- package/dist/commands/diff.js +38 -25
- package/dist/commands/diff.js.map +1 -1
- package/dist/commands/diff.test.js +72 -11
- package/dist/commands/diff.test.js.map +1 -1
- package/dist/commands/doctor.js +41 -27
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/doctor.test.js +93 -9
- package/dist/commands/doctor.test.js.map +1 -1
- package/dist/commands/eject.js +7 -0
- package/dist/commands/eject.js.map +1 -1
- package/dist/commands/eject.test.js +39 -0
- package/dist/commands/eject.test.js.map +1 -1
- package/dist/commands/info.js +3 -2
- package/dist/commands/info.js.map +1 -1
- package/dist/commands/init.d.ts +37 -1
- package/dist/commands/init.js +149 -40
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/init.test.js +285 -26
- package/dist/commands/init.test.js.map +1 -1
- package/dist/commands/install-packages.d.ts +6 -0
- package/dist/commands/install-packages.js +20 -0
- package/dist/commands/install-packages.js.map +1 -0
- package/dist/commands/install-packages.test.d.ts +1 -0
- package/dist/commands/install-packages.test.js +55 -0
- package/dist/commands/install-packages.test.js.map +1 -0
- package/dist/commands/list.js +21 -14
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/list.test.js +62 -5
- package/dist/commands/list.test.js.map +1 -1
- package/dist/commands/outdated.js +11 -10
- package/dist/commands/outdated.js.map +1 -1
- package/dist/commands/outdated.test.js +20 -5
- package/dist/commands/outdated.test.js.map +1 -1
- package/dist/commands/remove.d.ts +4 -0
- package/dist/commands/remove.js +17 -4
- package/dist/commands/remove.js.map +1 -1
- package/dist/commands/remove.test.js +53 -2
- package/dist/commands/remove.test.js.map +1 -1
- package/dist/commands/update.d.ts +6 -0
- package/dist/commands/update.js +59 -24
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/update.test.js +273 -31
- package/dist/commands/update.test.js.map +1 -1
- package/dist/index.js +10 -3
- package/dist/index.js.map +1 -1
- package/dist/registry.d.ts +8 -0
- package/dist/registry.js +32 -24
- package/dist/registry.js.map +1 -1
- package/dist/registry.test.js +76 -37
- package/dist/registry.test.js.map +1 -1
- package/dist/testing/set-input-signal.d.ts +13 -0
- package/dist/testing/set-input-signal.js +20 -0
- package/dist/testing/set-input-signal.js.map +1 -0
- package/dist/testing/set-input-signal.test.d.ts +1 -0
- package/dist/testing/set-input-signal.test.js +20 -0
- package/dist/testing/set-input-signal.test.js.map +1 -0
- package/dist/ui/components/action-sheet/action-sheet.test.ts +48 -1
- package/dist/ui/components/action-sheet/action-sheet.ts +37 -9
- package/dist/ui/components/alert-dialog/alert-dialog.test.ts +49 -2
- package/dist/ui/components/alert-dialog/alert-dialog.ts +43 -12
- package/dist/ui/components/area-chart/area-chart-render.test.ts +52 -110
- package/dist/ui/components/area-chart/area-chart.test.ts +59 -146
- package/dist/ui/components/area-chart/area-chart.ts +100 -315
- package/dist/ui/components/area-chart/index.ts +2 -3
- package/dist/ui/components/bar-chart/bar-chart-render.test.ts +47 -163
- package/dist/ui/components/bar-chart/bar-chart.test.ts +82 -58
- package/dist/ui/components/bar-chart/bar-chart.ts +140 -294
- package/dist/ui/components/bar-chart/index.ts +3 -3
- package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +191 -9
- package/dist/ui/components/bottom-sheet/bottom-sheet.ts +136 -20
- package/dist/ui/components/button/button-render.test.ts +19 -0
- package/dist/ui/components/button/button.test.ts +2 -15
- package/dist/ui/components/button/button.ts +18 -2
- package/dist/ui/components/button-group/button-group.test.ts +2 -21
- package/dist/ui/components/candlestick-chart/candlestick-chart-render.test.ts +48 -163
- package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +107 -85
- package/dist/ui/components/candlestick-chart/candlestick-chart.ts +150 -323
- package/dist/ui/components/candlestick-chart/index.ts +3 -4
- package/dist/ui/components/chart/chart-focus.test.ts +132 -0
- package/dist/ui/components/chart/chart-focus.ts +115 -0
- package/dist/ui/components/chart/chart-presets.test.ts +139 -0
- package/dist/ui/components/chart/chart-presets.ts +271 -0
- package/dist/ui/components/chart/chart-render.test.ts +730 -0
- package/dist/ui/components/chart/chart-scene.test.ts +589 -0
- package/dist/ui/components/chart/chart-scene.ts +483 -0
- package/dist/ui/components/chart/chart-theme.test.ts +85 -0
- package/dist/ui/components/chart/chart-theme.ts +198 -0
- package/dist/ui/components/chart/chart-zoom.test.ts +159 -0
- package/dist/ui/components/chart/chart-zoom.ts +162 -0
- package/dist/ui/components/chart/chart.ts +921 -0
- package/dist/ui/components/chart/index.ts +24 -0
- package/dist/ui/components/dialog/dialog.test.ts +49 -2
- package/dist/ui/components/dialog/dialog.ts +43 -12
- package/dist/ui/components/icon/icon-theme.test.ts +133 -0
- package/dist/ui/components/icon/icon-theme.ts +204 -0
- package/dist/ui/components/icon/icon.test.ts +51 -2
- package/dist/ui/components/icon/icon.ts +38 -21
- package/dist/ui/components/line-chart/index.ts +2 -3
- package/dist/ui/components/line-chart/line-chart-render.test.ts +69 -107
- package/dist/ui/components/line-chart/line-chart.test.ts +63 -67
- package/dist/ui/components/line-chart/line-chart.ts +83 -241
- package/dist/ui/components/list/list.test.ts +69 -2
- package/dist/ui/components/list/list.ts +21 -15
- package/dist/ui/components/nav-drawer/nav-drawer.test.ts +80 -1
- package/dist/ui/components/nav-drawer/nav-drawer.ts +66 -11
- package/dist/ui/components/pagination/pagination.test.ts +62 -0
- package/dist/ui/components/pagination/pagination.ts +55 -8
- package/dist/ui/components/scatter-chart/index.ts +2 -3
- package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +51 -144
- package/dist/ui/components/scatter-chart/scatter-chart.test.ts +57 -47
- package/dist/ui/components/scatter-chart/scatter-chart.ts +104 -250
- package/dist/ui/components/scroll-area/scroll-area.test.ts +37 -2
- package/dist/ui/components/scroll-area/scroll-area.ts +8 -8
- package/dist/ui/components/spinner/spinner.test.ts +27 -0
- package/dist/ui/components/spinner/spinner.ts +33 -23
- package/dist/ui/components/toast/toast.test.ts +37 -0
- package/dist/ui/components/toast/toast.ts +20 -4
- package/dist/ui/theme/chart-colors.test.ts +30 -0
- package/dist/ui/theme/dark.css +8 -0
- package/dist/ui/theme/default.css +8 -0
- package/dist/ui/theme/icon-colors.test.ts +30 -0
- package/dist/ui/theme/tailwind-plugin.ts +10 -1
- package/dist/utils/installed.d.ts +17 -0
- package/dist/utils/installed.js +27 -0
- package/dist/utils/installed.js.map +1 -0
- package/dist/utils/installed.test.d.ts +1 -0
- package/dist/utils/installed.test.js +41 -0
- package/dist/utils/installed.test.js.map +1 -0
- package/dist/utils/interactive.d.ts +9 -0
- package/dist/utils/interactive.js +10 -0
- package/dist/utils/interactive.js.map +1 -0
- package/dist/utils/interactive.test.d.ts +1 -0
- package/dist/utils/interactive.test.js +24 -0
- package/dist/utils/interactive.test.js.map +1 -0
- package/dist/utils/packages.d.ts +36 -0
- package/dist/utils/packages.js +70 -0
- package/dist/utils/packages.js.map +1 -0
- package/dist/utils/packages.test.d.ts +1 -0
- package/dist/utils/packages.test.js +89 -0
- package/dist/utils/packages.test.js.map +1 -0
- package/dist/utils/resolve-paths.d.ts +17 -1
- package/dist/utils/resolve-paths.js +23 -3
- package/dist/utils/resolve-paths.js.map +1 -1
- package/dist/utils/resolve-paths.test.js +34 -2
- package/dist/utils/resolve-paths.test.js.map +1 -1
- package/package.json +5 -1
- package/dist/ui/components/cartesian-chart/cartesian-chart-render.test.ts +0 -188
- package/dist/ui/components/cartesian-chart/cartesian-chart.test.ts +0 -549
- package/dist/ui/components/cartesian-chart/cartesian-chart.ts +0 -1140
- package/dist/ui/components/cartesian-chart/index.ts +0 -18
|
@@ -9,13 +9,15 @@ import {
|
|
|
9
9
|
} from '@angular/core';
|
|
10
10
|
import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
|
|
11
11
|
|
|
12
|
+
import { cn } from '../../utils/cn';
|
|
13
|
+
import { injectIconColor } from '../icon/icon-theme';
|
|
14
|
+
|
|
12
15
|
const LOADER_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v4"/><path d="m16.2 7.8 2.9-2.9"/><path d="M18 12h4"/><path d="m16.2 16.2 2.9 2.9"/><path d="M12 18v4"/><path d="m4.9 19.1 2.9-2.9"/><path d="M2 12h4"/><path d="m4.9 4.9 2.9 2.9"/></svg>`;
|
|
13
16
|
|
|
14
17
|
const SIZE_MAP = { xs: 16, sm: 20, md: 24, lg: 32 } as const;
|
|
15
18
|
|
|
16
19
|
@Component({
|
|
17
20
|
selector: 'ui-spinner',
|
|
18
|
-
standalone: true,
|
|
19
21
|
imports: [LYNX_ELEMENTS],
|
|
20
22
|
encapsulation: ViewEncapsulation.None,
|
|
21
23
|
// SVG markup is passed via [attr.content] rather than as child elements.
|
|
@@ -24,14 +26,22 @@ const SIZE_MAP = { xs: 16, sm: 20, md: 24, lg: 32 } as const;
|
|
|
24
26
|
// SVG markup string as the `content` attribute is the Lynx-native way to
|
|
25
27
|
// embed vector graphics.
|
|
26
28
|
template: `
|
|
27
|
-
<view #spinner [style]="sizeStyle()">
|
|
29
|
+
<view #spinner [class]="containerClass()" [style]="sizeStyle()">
|
|
28
30
|
<svg [attr.content]="svgContent()" style="width: 100%; height: 100%;" />
|
|
29
31
|
</view>
|
|
30
32
|
`,
|
|
31
33
|
})
|
|
32
34
|
export class UiSpinner {
|
|
33
35
|
readonly size = input<'xs' | 'sm' | 'md' | 'lg'>('md');
|
|
36
|
+
/**
|
|
37
|
+
* A literal color or a theme token (`var(--primary)`). Without one, the
|
|
38
|
+
* spinner takes the color its container sets (see `UI_ICON_COLOR`), then the
|
|
39
|
+
* theme's foreground.
|
|
40
|
+
*/
|
|
34
41
|
readonly color = input<string | undefined>(undefined);
|
|
42
|
+
readonly userClass = input<string>('', { alias: 'class' });
|
|
43
|
+
|
|
44
|
+
readonly #resolveColor = injectIconColor();
|
|
35
45
|
|
|
36
46
|
readonly spinnerRef = viewChild<ElementRef>('spinner');
|
|
37
47
|
|
|
@@ -54,27 +64,27 @@ export class UiSpinner {
|
|
|
54
64
|
});
|
|
55
65
|
}
|
|
56
66
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
67
|
+
// Lynx SVG elements don't inherit the CSS `color` property into their stroke
|
|
68
|
+
// attributes, so we substitute the literal `currentColor` in the SVG string
|
|
69
|
+
// rather than relying on `currentColor` propagation via CSS. The color is
|
|
70
|
+
// resolved first: the SVG parser can't read `var(--x)` either (see
|
|
71
|
+
// icon-theme.ts).
|
|
72
|
+
//
|
|
73
|
+
// Uses split().join() instead of String.prototype.replaceAll(). replaceAll is
|
|
74
|
+
// ES2021, but Lynx's main thread runs on PrimJS at an ES2019 target; because
|
|
75
|
+
// it's a runtime method (not syntax) the build's down-leveling does not
|
|
76
|
+
// polyfill it, so it simply doesn't exist there. Calling it throws
|
|
77
|
+
// "main-thread.js exception: not a function" from inside Angular change
|
|
78
|
+
// detection, which aborts the whole tick before Lynx flushes the element tree
|
|
79
|
+
// — so the spinner never paints, and the error count climbs on every render
|
|
80
|
+
// as each tick re-runs and re-throws. split/join is ES2019-safe and is
|
|
81
|
+
// preferred over `replace(/.../g)` here because the search term is a plain
|
|
82
|
+
// literal and needs no regex escaping.
|
|
83
|
+
protected readonly svgContent = computed(() =>
|
|
84
|
+
LOADER_SVG.split('currentColor').join(this.#resolveColor(this.color())),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
protected readonly containerClass = computed(() => cn(this.userClass()));
|
|
78
88
|
|
|
79
89
|
protected readonly sizeStyle = computed(() => {
|
|
80
90
|
const px = SIZE_MAP[this.size()];
|
|
@@ -154,6 +154,43 @@ describe('UiToastItem', () => {
|
|
|
154
154
|
expect(dismissed).toHaveBeenCalled();
|
|
155
155
|
});
|
|
156
156
|
|
|
157
|
+
it('stops its auto-dismiss timer when it is destroyed first', async () => {
|
|
158
|
+
// dismissToast(id) removes the toast at once. Its timer used to fire on
|
|
159
|
+
// the destroyed component anyway, and emit from a destroyed output.
|
|
160
|
+
const dismissed = vi.fn();
|
|
161
|
+
const warn = vi.spyOn(console, 'warn');
|
|
162
|
+
const { componentRef } = await render(UiToastItem);
|
|
163
|
+
const inst = componentRef.instance as UiToastItem;
|
|
164
|
+
inst.dismissed.subscribe(dismissed);
|
|
165
|
+
setInputSignal(inst.data, baseData({ duration: 30 }));
|
|
166
|
+
setInputSignal(inst.depth, 0);
|
|
167
|
+
await waitForUpdate();
|
|
168
|
+
inst.ngOnInit();
|
|
169
|
+
componentRef.destroy();
|
|
170
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
171
|
+
expect(dismissed).not.toHaveBeenCalled();
|
|
172
|
+
expect(warn).not.toHaveBeenCalledWith(expect.stringContaining('NG0953'));
|
|
173
|
+
warn.mockRestore();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('stops its exit timer when it is destroyed mid-exit', async () => {
|
|
177
|
+
// A destroyed output drops the emit, so the warning is what shows it.
|
|
178
|
+
const dismissed = vi.fn();
|
|
179
|
+
const warn = vi.spyOn(console, 'warn');
|
|
180
|
+
const { componentRef } = await render(UiToastItem);
|
|
181
|
+
const inst = componentRef.instance as UiToastItem;
|
|
182
|
+
inst.dismissed.subscribe(dismissed);
|
|
183
|
+
setInputSignal(inst.data, baseData());
|
|
184
|
+
setInputSignal(inst.depth, 0);
|
|
185
|
+
await waitForUpdate();
|
|
186
|
+
inst.dismiss();
|
|
187
|
+
componentRef.destroy();
|
|
188
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
189
|
+
expect(dismissed).not.toHaveBeenCalled();
|
|
190
|
+
expect(warn).not.toHaveBeenCalledWith(expect.stringContaining('NG0953'));
|
|
191
|
+
warn.mockRestore();
|
|
192
|
+
});
|
|
193
|
+
|
|
157
194
|
it('touch drag beyond threshold dismisses; below threshold snaps back', async () => {
|
|
158
195
|
const { componentRef } = await render(UiToastItem);
|
|
159
196
|
const inst = componentRef.instance as UiToastItem;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type OnDestroy,
|
|
2
3
|
type OnInit,
|
|
3
4
|
Component,
|
|
4
5
|
ElementRef,
|
|
@@ -53,7 +54,6 @@ const stackTransform = (depth: number): string =>
|
|
|
53
54
|
*/
|
|
54
55
|
@Component({
|
|
55
56
|
selector: 'ui-toast-item',
|
|
56
|
-
standalone: true,
|
|
57
57
|
imports: [LYNX_ELEMENTS],
|
|
58
58
|
encapsulation: ViewEncapsulation.None,
|
|
59
59
|
// Styling + interaction live on the HOST element (a plain `view`, since the
|
|
@@ -86,7 +86,7 @@ const stackTransform = (depth: number): string =>
|
|
|
86
86
|
}
|
|
87
87
|
`,
|
|
88
88
|
})
|
|
89
|
-
export class UiToastItem implements OnInit {
|
|
89
|
+
export class UiToastItem implements OnInit, OnDestroy {
|
|
90
90
|
readonly data = input.required<ToastData>();
|
|
91
91
|
// 0 = front (newest, interactive); higher = further back in the stack.
|
|
92
92
|
readonly depth = input.required<number>();
|
|
@@ -96,6 +96,8 @@ export class UiToastItem implements OnInit {
|
|
|
96
96
|
readonly #host = inject(ElementRef);
|
|
97
97
|
#anim?: AnimationHandle;
|
|
98
98
|
#dismissTimer: ReturnType<typeof setTimeout> | null = null;
|
|
99
|
+
// The one that emits `dismissed` once the exit animation has played.
|
|
100
|
+
#exitTimer: ReturnType<typeof setTimeout> | null = null;
|
|
99
101
|
|
|
100
102
|
// Hidden until the entrance animation runs, so the toast doesn't flash at its
|
|
101
103
|
// resting position for a frame before sliding/fading in.
|
|
@@ -295,7 +297,22 @@ export class UiToastItem implements OnInit {
|
|
|
295
297
|
this.#dismissTimer = null;
|
|
296
298
|
}
|
|
297
299
|
this.#animateOut();
|
|
298
|
-
|
|
300
|
+
this.#exitTimer = setTimeout(
|
|
301
|
+
() => this.dismissed.emit(),
|
|
302
|
+
DURATION.fast + 20,
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* A toast can be destroyed before its timers fire: `dismissToast(id)` drops
|
|
308
|
+
* it from the queue straight away, and the toaster itself can be removed.
|
|
309
|
+
* Without this, the auto-dismiss timer later ran `dismiss()` on the destroyed
|
|
310
|
+
* component: it animated a detached element and emitted `dismissed` from a
|
|
311
|
+
* destroyed output, which Angular warns about in dev builds (NG0953).
|
|
312
|
+
*/
|
|
313
|
+
ngOnDestroy(): void {
|
|
314
|
+
if (this.#dismissTimer) clearTimeout(this.#dismissTimer);
|
|
315
|
+
if (this.#exitTimer) clearTimeout(this.#exitTimer);
|
|
299
316
|
}
|
|
300
317
|
|
|
301
318
|
#animateIn(): void {
|
|
@@ -447,7 +464,6 @@ export class UiToastItem implements OnInit {
|
|
|
447
464
|
*/
|
|
448
465
|
@Component({
|
|
449
466
|
selector: 'ui-toaster',
|
|
450
|
-
standalone: true,
|
|
451
467
|
imports: [LYNX_ELEMENTS, UiToastItem],
|
|
452
468
|
encapsulation: ViewEncapsulation.None,
|
|
453
469
|
template: `
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import {
|
|
4
|
+
CHART_DARK_COLORS,
|
|
5
|
+
CHART_LIGHT_COLORS,
|
|
6
|
+
} from '../components/chart/chart-theme';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* `<ui-chart>` draws through native SVG, which can't read CSS variables, so the
|
|
10
|
+
* chart component keeps a TypeScript copy of the theme's colors. This keeps the
|
|
11
|
+
* copy in step with default.css and dark.css.
|
|
12
|
+
*/
|
|
13
|
+
const tokens = (file: string): Record<string, string> => {
|
|
14
|
+
const css = readFileSync(new URL(file, import.meta.url), 'utf8');
|
|
15
|
+
const values: Record<string, string> = {};
|
|
16
|
+
for (const match of css.matchAll(/--([\w-]+):\s*(rgba\([^)]*\))/g)) {
|
|
17
|
+
values[match[1]!] = match[2]!;
|
|
18
|
+
}
|
|
19
|
+
return values;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
describe('chart color tables', () => {
|
|
23
|
+
it('match default.css', () => {
|
|
24
|
+
expect(CHART_LIGHT_COLORS).toEqual(tokens('./default.css'));
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('match dark.css', () => {
|
|
28
|
+
expect(CHART_DARK_COLORS).toEqual(tokens('./dark.css'));
|
|
29
|
+
});
|
|
30
|
+
});
|
package/dist/ui/theme/dark.css
CHANGED
|
@@ -37,5 +37,13 @@ page.dark {
|
|
|
37
37
|
/* Translucent variant of --ring for a softer focus outline (see default.css). */
|
|
38
38
|
--ring-subtle: rgba(212, 212, 216, 0.4);
|
|
39
39
|
|
|
40
|
+
/* Series palette for charts (bg-chart-1, and chart-theme.ts in the chart
|
|
41
|
+
component, which mirrors these values for SVG). */
|
|
42
|
+
--chart-1: rgba(38, 98, 217, 1);
|
|
43
|
+
--chart-2: rgba(46, 184, 138, 1);
|
|
44
|
+
--chart-3: rgba(232, 140, 48, 1);
|
|
45
|
+
--chart-4: rgba(175, 87, 219, 1);
|
|
46
|
+
--chart-5: rgba(226, 54, 112, 1);
|
|
47
|
+
|
|
40
48
|
--radius: 8px;
|
|
41
49
|
}
|
|
@@ -52,5 +52,13 @@ page {
|
|
|
52
52
|
apply to the opaque --ring, and Lynx can't inject alpha into it either. */
|
|
53
53
|
--ring-subtle: rgba(24, 24, 27, 0.4);
|
|
54
54
|
|
|
55
|
+
/* Series palette for charts (bg-chart-1, and chart-theme.ts in the chart
|
|
56
|
+
component, which mirrors these values for SVG). */
|
|
57
|
+
--chart-1: rgba(231, 110, 80, 1);
|
|
58
|
+
--chart-2: rgba(42, 157, 144, 1);
|
|
59
|
+
--chart-3: rgba(39, 71, 84, 1);
|
|
60
|
+
--chart-4: rgba(232, 196, 104, 1);
|
|
61
|
+
--chart-5: rgba(244, 164, 98, 1);
|
|
62
|
+
|
|
55
63
|
--radius: 8px;
|
|
56
64
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import {
|
|
4
|
+
ICON_DARK_COLORS,
|
|
5
|
+
ICON_LIGHT_COLORS,
|
|
6
|
+
} from '../components/icon/icon-theme';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* `<ui-icon>`, `<ui-spinner>` and pagination draw through native SVG, which
|
|
10
|
+
* can't read CSS variables, so icon-theme.ts keeps a TypeScript copy of the
|
|
11
|
+
* theme's colors. This keeps the copy in step with default.css and dark.css.
|
|
12
|
+
*/
|
|
13
|
+
const tokens = (file: string): Record<string, string> => {
|
|
14
|
+
const css = readFileSync(new URL(file, import.meta.url), 'utf8');
|
|
15
|
+
const values: Record<string, string> = {};
|
|
16
|
+
for (const match of css.matchAll(/--([\w-]+):\s*(rgba\([^)]*\))/g)) {
|
|
17
|
+
values[match[1]!] = match[2]!;
|
|
18
|
+
}
|
|
19
|
+
return values;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
describe('icon color tables', () => {
|
|
23
|
+
it('match default.css', () => {
|
|
24
|
+
expect(ICON_LIGHT_COLORS).toEqual(tokens('./default.css'));
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('match dark.css', () => {
|
|
28
|
+
expect(ICON_DARK_COLORS).toEqual(tokens('./dark.css'));
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -16,7 +16,7 @@ const SAFE_AREA_SIDES = {
|
|
|
16
16
|
* Registers safe-area-aware padding utilities driven by CSS `env(safe-area-inset-*)`.
|
|
17
17
|
*
|
|
18
18
|
* Why `env()` works on Lynx: the native CSS engine parses `env(safe-area-inset-*)`
|
|
19
|
-
* as a length (see
|
|
19
|
+
* as a length (see lynx-family/lynx's css_style_utils.cc `GetEnvValue`), and its
|
|
20
20
|
* calc parser accepts `env()` *inside* `calc()` too — so `calc(1rem + env(...))`
|
|
21
21
|
* is valid on device. The inset values default to `0` until the native host app
|
|
22
22
|
* populates them, so every utility here degrades gracefully to plain padding
|
|
@@ -139,6 +139,15 @@ export const blotchPlugin = plugin(
|
|
|
139
139
|
DEFAULT: 'var(--card)',
|
|
140
140
|
foreground: 'var(--card-foreground)',
|
|
141
141
|
},
|
|
142
|
+
// Chart series colors, e.g. bg-chart-1 for a legend swatch that
|
|
143
|
+
// matches the first series.
|
|
144
|
+
chart: {
|
|
145
|
+
1: 'var(--chart-1)',
|
|
146
|
+
2: 'var(--chart-2)',
|
|
147
|
+
3: 'var(--chart-3)',
|
|
148
|
+
4: 'var(--chart-4)',
|
|
149
|
+
5: 'var(--chart-5)',
|
|
150
|
+
},
|
|
142
151
|
},
|
|
143
152
|
borderRadius: {
|
|
144
153
|
lg: 'var(--radius)',
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Lockfile } from '../lockfile.js';
|
|
2
|
+
/**
|
|
3
|
+
* The registry components with a folder in the components directory. Other
|
|
4
|
+
* folders (the app's own code next to the components) aren't dolan's.
|
|
5
|
+
*/
|
|
6
|
+
export declare const componentsOnDisk: (componentsDir: string) => string[];
|
|
7
|
+
/**
|
|
8
|
+
* The installed components dolan tracks: on disk and in the lockfile. `add`
|
|
9
|
+
* records every component it copies, and `eject` removes the record to hand
|
|
10
|
+
* the component over to the app, so a folder without one is the app's own
|
|
11
|
+
* code. `update`, `list`, `diff` and `outdated` leave it alone: before, they
|
|
12
|
+
* scanned the folders, so an ejected component showed as a conflict,
|
|
13
|
+
* `update --force` overwrote it and `update` tracked it again.
|
|
14
|
+
*/
|
|
15
|
+
export declare const trackedComponents: (componentsDir: string, lockfile: Lockfile) => string[];
|
|
16
|
+
/** The components on disk that dolan doesn't track (see trackedComponents). */
|
|
17
|
+
export declare const untrackedComponents: (componentsDir: string, lockfile: Lockfile) => string[];
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { getComponentNames } from '../registry.js';
|
|
3
|
+
/**
|
|
4
|
+
* The registry components with a folder in the components directory. Other
|
|
5
|
+
* folders (the app's own code next to the components) aren't dolan's.
|
|
6
|
+
*/
|
|
7
|
+
export const componentsOnDisk = (componentsDir) => {
|
|
8
|
+
if (!existsSync(componentsDir))
|
|
9
|
+
return [];
|
|
10
|
+
const known = new Set(getComponentNames());
|
|
11
|
+
return readdirSync(componentsDir, { withFileTypes: true })
|
|
12
|
+
.filter((entry) => entry.isDirectory() && known.has(entry.name))
|
|
13
|
+
.map((entry) => entry.name)
|
|
14
|
+
.sort();
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* The installed components dolan tracks: on disk and in the lockfile. `add`
|
|
18
|
+
* records every component it copies, and `eject` removes the record to hand
|
|
19
|
+
* the component over to the app, so a folder without one is the app's own
|
|
20
|
+
* code. `update`, `list`, `diff` and `outdated` leave it alone: before, they
|
|
21
|
+
* scanned the folders, so an ejected component showed as a conflict,
|
|
22
|
+
* `update --force` overwrote it and `update` tracked it again.
|
|
23
|
+
*/
|
|
24
|
+
export const trackedComponents = (componentsDir, lockfile) => componentsOnDisk(componentsDir).filter((name) => lockfile.components[name]);
|
|
25
|
+
/** The components on disk that dolan doesn't track (see trackedComponents). */
|
|
26
|
+
export const untrackedComponents = (componentsDir, lockfile) => componentsOnDisk(componentsDir).filter((name) => !lockfile.components[name]);
|
|
27
|
+
//# sourceMappingURL=installed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"installed.js","sourceRoot":"","sources":["../../src/utils/installed.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAGnD;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,aAAqB,EAAY,EAAE;IAClE,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC;QAAE,OAAO,EAAE,CAAC;IAC1C,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,iBAAiB,EAAE,CAAC,CAAC;IAC3C,OAAO,WAAW,CAAC,aAAa,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;SACvD,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;SAC/D,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC;SAC1B,IAAI,EAAE,CAAC;AACZ,CAAC,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,aAAqB,EACrB,QAAkB,EACR,EAAE,CACZ,gBAAgB,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;AAE9E,+EAA+E;AAC/E,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,aAAqB,EACrB,QAAkB,EACR,EAAE,CACZ,gBAAgB,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { createEmptyLockfile } from '../lockfile';
|
|
6
|
+
import { componentsOnDisk, trackedComponents, untrackedComponents, } from './installed';
|
|
7
|
+
let root;
|
|
8
|
+
const componentsDir = (...folders) => {
|
|
9
|
+
root = mkdtempSync(join(tmpdir(), 'dolan-installed-'));
|
|
10
|
+
for (const folder of folders)
|
|
11
|
+
mkdirSync(join(root, folder));
|
|
12
|
+
// A file isn't a component, whatever its name.
|
|
13
|
+
writeFileSync(join(root, 'badge'), '');
|
|
14
|
+
return root;
|
|
15
|
+
};
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
if (root)
|
|
18
|
+
rmSync(root, { recursive: true, force: true });
|
|
19
|
+
});
|
|
20
|
+
describe('componentsOnDisk', () => {
|
|
21
|
+
it('lists the registry components with a folder, sorted', () => {
|
|
22
|
+
const dir = componentsDir('spinner', 'card', 'my-own-thing');
|
|
23
|
+
expect(componentsOnDisk(dir)).toEqual(['card', 'spinner']);
|
|
24
|
+
});
|
|
25
|
+
it('is empty when the directory is missing', () => {
|
|
26
|
+
expect(componentsOnDisk(join(tmpdir(), 'dolan-missing-dir'))).toEqual([]);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe('trackedComponents / untrackedComponents', () => {
|
|
30
|
+
it('splits the components on disk by their lockfile entry', () => {
|
|
31
|
+
const dir = componentsDir('card', 'spinner', 'button');
|
|
32
|
+
const lockfile = createEmptyLockfile();
|
|
33
|
+
lockfile.components['card'] = {};
|
|
34
|
+
lockfile.components['button'] = {};
|
|
35
|
+
// In the lockfile but not on disk: removed by hand, so neither.
|
|
36
|
+
lockfile.components['dialog'] = {};
|
|
37
|
+
expect(trackedComponents(dir, lockfile)).toEqual(['button', 'card']);
|
|
38
|
+
expect(untrackedComponents(dir, lockfile)).toEqual(['spinner']);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
//# sourceMappingURL=installed.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"installed.test.js","sourceRoot":"","sources":["../../src/utils/installed.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,aAAa,CAAC;AAErB,IAAI,IAAY,CAAC;AACjB,MAAM,aAAa,GAAG,CAAC,GAAG,OAAiB,EAAE,EAAE;IAC7C,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAC;IACvD,KAAK,MAAM,MAAM,IAAI,OAAO;QAAE,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAC5D,+CAA+C;IAC/C,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;IACvC,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF,SAAS,CAAC,GAAG,EAAE;IACb,IAAI,IAAI;QAAE,MAAM,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AAC3D,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,GAAG,GAAG,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;QAC7D,MAAM,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,CAAC,gBAAgB,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC5E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,yCAAyC,EAAE,GAAG,EAAE;IACvD,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,GAAG,GAAG,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;QACvD,MAAM,QAAQ,GAAG,mBAAmB,EAAE,CAAC;QACvC,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;QACjC,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC;QACnC,gEAAgE;QAChE,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC;QAEnC,MAAM,CAAC,iBAAiB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;QACrE,MAAM,CAAC,mBAAmB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether anyone can answer a question: stdin is a terminal. In CI, a script
|
|
3
|
+
* or an AI agent it isn't, and clack's prompts wait for input that never
|
|
4
|
+
* comes. The commands used to hang there until stdin closed, then exit 0
|
|
5
|
+
* having done nothing. Without a terminal, each command asks nothing and takes
|
|
6
|
+
* each question's default instead, or exits 1 and names the option to pass
|
|
7
|
+
* when that default would cancel the run.
|
|
8
|
+
*/
|
|
9
|
+
export declare const isInteractive: () => boolean;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether anyone can answer a question: stdin is a terminal. In CI, a script
|
|
3
|
+
* or an AI agent it isn't, and clack's prompts wait for input that never
|
|
4
|
+
* comes. The commands used to hang there until stdin closed, then exit 0
|
|
5
|
+
* having done nothing. Without a terminal, each command asks nothing and takes
|
|
6
|
+
* each question's default instead, or exits 1 and names the option to pass
|
|
7
|
+
* when that default would cancel the run.
|
|
8
|
+
*/
|
|
9
|
+
export const isInteractive = () => process.stdin.isTTY === true;
|
|
10
|
+
//# sourceMappingURL=interactive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interactive.js","sourceRoot":"","sources":["../../src/utils/interactive.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,GAAY,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { isInteractive } from './interactive';
|
|
3
|
+
const isTTY = Object.getOwnPropertyDescriptor(process.stdin, 'isTTY');
|
|
4
|
+
const setIsTTY = (value) => Object.defineProperty(process.stdin, 'isTTY', { value, configurable: true });
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
if (isTTY)
|
|
7
|
+
Object.defineProperty(process.stdin, 'isTTY', isTTY);
|
|
8
|
+
else
|
|
9
|
+
delete process.stdin.isTTY;
|
|
10
|
+
});
|
|
11
|
+
describe('isInteractive', () => {
|
|
12
|
+
it('is true when stdin is a terminal', () => {
|
|
13
|
+
setIsTTY(true);
|
|
14
|
+
expect(isInteractive()).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
it('is false when stdin is a pipe or a file', () => {
|
|
17
|
+
// Node leaves `isTTY` undefined on anything but a terminal.
|
|
18
|
+
setIsTTY(undefined);
|
|
19
|
+
expect(isInteractive()).toBe(false);
|
|
20
|
+
setIsTTY(false);
|
|
21
|
+
expect(isInteractive()).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
//# sourceMappingURL=interactive.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interactive.test.js","sourceRoot":"","sources":["../../src/utils/interactive.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C,MAAM,KAAK,GAAG,MAAM,CAAC,wBAAwB,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;AACtE,MAAM,QAAQ,GAAG,CAAC,KAA0B,EAAE,EAAE,CAC9C,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;AAE/E,SAAS,CAAC,GAAG,EAAE;IACb,IAAI,KAAK;QAAE,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;;QAC3D,OAAQ,OAAO,CAAC,KAA6B,CAAC,KAAK,CAAC;AAC3D,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,QAAQ,CAAC,IAAI,CAAC,CAAC;QACf,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,4DAA4D;QAC5D,QAAQ,CAAC,SAAS,CAAC,CAAC;QACpB,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAChB,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
/**
|
|
3
|
+
* npm packages a component imports. Components are copied, not installed, but
|
|
4
|
+
* some build on a library (the charts on `@tanstack/charts`), and their copied
|
|
5
|
+
* code can't compile until the project depends on it too. `dolan add` and
|
|
6
|
+
* `dolan update` install what's missing with the project's own package manager.
|
|
7
|
+
*/
|
|
8
|
+
export type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun';
|
|
9
|
+
/**
|
|
10
|
+
* The package manager whose lockfile is nearest `cwd`, looking up the tree
|
|
11
|
+
* because in a workspace the lockfile sits at the root. npm when there's none.
|
|
12
|
+
*/
|
|
13
|
+
export declare const detectPackageManager: (cwd: string) => PackageManager;
|
|
14
|
+
/** `@scope/name@^1.0.0` → `@scope/name`; `name` stays `name`. */
|
|
15
|
+
export declare const packageName: (spec: string) => string;
|
|
16
|
+
/** The specs whose package the project's package.json doesn't list yet. */
|
|
17
|
+
export declare const missingPackages: (cwd: string, specs: readonly string[]) => string[];
|
|
18
|
+
/** The command that adds `specs` as dependencies, e.g. `npm install a b`. */
|
|
19
|
+
export declare const installCommand: (manager: PackageManager, specs: readonly string[]) => string[];
|
|
20
|
+
/**
|
|
21
|
+
* Installs `specs` in `cwd`. Returns false when the package manager couldn't
|
|
22
|
+
* run or failed, so the caller can show the command to run by hand.
|
|
23
|
+
*/
|
|
24
|
+
export declare const installPackages: (cwd: string, specs: readonly string[], run?: typeof spawnSync) => boolean;
|
|
25
|
+
/**
|
|
26
|
+
* The library the components' tests render with. Apps get it from
|
|
27
|
+
* `ng generate @blotch/angular-lynx:add-testing`.
|
|
28
|
+
*/
|
|
29
|
+
export declare const TESTING_LIBRARY = "@blotch/angular-lynx-testing-library";
|
|
30
|
+
/**
|
|
31
|
+
* Whether dolan copies components' tests into the project: only when it
|
|
32
|
+
* depends on the testing library, since the tests import it and `ng test`
|
|
33
|
+
* fails to compile them otherwise. Every command asks the same question, so
|
|
34
|
+
* a project without the library never sees the tests as missing or new.
|
|
35
|
+
*/
|
|
36
|
+
export declare const copiesTests: (cwd: string) => boolean;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
const LOCK_FILES = [
|
|
5
|
+
['pnpm-lock.yaml', 'pnpm'],
|
|
6
|
+
['yarn.lock', 'yarn'],
|
|
7
|
+
['bun.lock', 'bun'],
|
|
8
|
+
['bun.lockb', 'bun'],
|
|
9
|
+
['package-lock.json', 'npm'],
|
|
10
|
+
];
|
|
11
|
+
/**
|
|
12
|
+
* The package manager whose lockfile is nearest `cwd`, looking up the tree
|
|
13
|
+
* because in a workspace the lockfile sits at the root. npm when there's none.
|
|
14
|
+
*/
|
|
15
|
+
export const detectPackageManager = (cwd) => {
|
|
16
|
+
let dir = cwd;
|
|
17
|
+
for (;;) {
|
|
18
|
+
for (const [file, manager] of LOCK_FILES) {
|
|
19
|
+
if (existsSync(join(dir, file)))
|
|
20
|
+
return manager;
|
|
21
|
+
}
|
|
22
|
+
const parent = dirname(dir);
|
|
23
|
+
if (parent === dir)
|
|
24
|
+
return 'npm';
|
|
25
|
+
dir = parent;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
/** `@scope/name@^1.0.0` → `@scope/name`; `name` stays `name`. */
|
|
29
|
+
export const packageName = (spec) => {
|
|
30
|
+
const at = spec.indexOf('@', 1);
|
|
31
|
+
return at === -1 ? spec : spec.slice(0, at);
|
|
32
|
+
};
|
|
33
|
+
/** The specs whose package the project's package.json doesn't list yet. */
|
|
34
|
+
export const missingPackages = (cwd, specs) => {
|
|
35
|
+
const manifestPath = join(cwd, 'package.json');
|
|
36
|
+
const manifest = existsSync(manifestPath)
|
|
37
|
+
? JSON.parse(readFileSync(manifestPath, 'utf-8'))
|
|
38
|
+
: {};
|
|
39
|
+
const listed = new Set(['dependencies', 'devDependencies', 'peerDependencies'].flatMap((key) => Object.keys(manifest[key] ?? {})));
|
|
40
|
+
return specs.filter((spec) => !listed.has(packageName(spec)));
|
|
41
|
+
};
|
|
42
|
+
/** The command that adds `specs` as dependencies, e.g. `npm install a b`. */
|
|
43
|
+
export const installCommand = (manager, specs) => [manager, manager === 'npm' ? 'install' : 'add', ...specs];
|
|
44
|
+
/**
|
|
45
|
+
* Installs `specs` in `cwd`. Returns false when the package manager couldn't
|
|
46
|
+
* run or failed, so the caller can show the command to run by hand.
|
|
47
|
+
*/
|
|
48
|
+
export const installPackages = (cwd, specs, run = spawnSync) => {
|
|
49
|
+
const [command, ...args] = installCommand(detectPackageManager(cwd), specs);
|
|
50
|
+
const result = run(command, args, {
|
|
51
|
+
cwd,
|
|
52
|
+
stdio: 'inherit',
|
|
53
|
+
// Package managers are .cmd shims on Windows, which only a shell runs.
|
|
54
|
+
shell: process.platform === 'win32',
|
|
55
|
+
});
|
|
56
|
+
return result.status === 0;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* The library the components' tests render with. Apps get it from
|
|
60
|
+
* `ng generate @blotch/angular-lynx:add-testing`.
|
|
61
|
+
*/
|
|
62
|
+
export const TESTING_LIBRARY = '@blotch/angular-lynx-testing-library';
|
|
63
|
+
/**
|
|
64
|
+
* Whether dolan copies components' tests into the project: only when it
|
|
65
|
+
* depends on the testing library, since the tests import it and `ng test`
|
|
66
|
+
* fails to compile them otherwise. Every command asks the same question, so
|
|
67
|
+
* a project without the library never sees the tests as missing or new.
|
|
68
|
+
*/
|
|
69
|
+
export const copiesTests = (cwd) => missingPackages(cwd, [TESTING_LIBRARY]).length === 0;
|
|
70
|
+
//# sourceMappingURL=packages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"packages.js","sourceRoot":"","sources":["../../src/utils/packages.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|