@yahoo/uds 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- package/cli/README.md +2 -8
- package/cli/bunfig.toml +3 -0
- package/cli/commands/expo/_setup.ts +1 -0
- package/cli/commands/expo/build.ts +1 -1
- package/cli/commands/expo/launch.ts +1 -1
- package/cli/commands/purge.ts +1 -1
- package/cli/commands/sync.ts +16 -4
- package/cli/commands/uds.ts +4 -1
- package/cli/commands/version.ts +1 -1
- package/cli/preload.ts +42 -0
- package/cli/utils/configWorker.ts +30 -11
- package/cli/utils/getCommandHelp.ts +22 -13
- package/cli/utils/purgeCSS.test.ts +139 -0
- package/cli/utils/purgeCSS.ts +72 -8
- package/cli/utils/setupConfigWorker.ts +8 -38
- package/cli/utils/types.ts +5 -2
- package/dist/{Image.native-tkOXN29I.d.ts → Image.native-C6kOWgnf.d.ts} +1 -1
- package/dist/{Image.native-jCNIrPZD.d.cts → Image.native-VeXt5aeI.d.cts} +1 -1
- package/dist/VStack-BSD9TbBd.d.cts +114 -0
- package/dist/VStack-Dk3-8IyU.d.ts +114 -0
- package/dist/experimental/index.cjs +1 -1
- package/dist/experimental/index.d.cts +5 -4
- package/dist/experimental/index.d.ts +5 -4
- package/dist/experimental/index.js +1 -1
- package/dist/experimental/index.native.cjs +1 -1
- package/dist/experimental/index.native.d.cts +3 -3
- package/dist/experimental/index.native.d.ts +3 -3
- package/dist/experimental/index.native.js +1 -1
- package/dist/fixtures/index.cjs +1 -1
- package/dist/fixtures/index.d.cts +15 -1
- package/dist/fixtures/index.d.ts +15 -1
- package/dist/fixtures/index.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +133 -184
- package/dist/index.d.ts +133 -184
- package/dist/index.js +1 -1
- package/dist/{index.native-xVHqKK0u.d.ts → index.native-CisPq4BI.d.ts} +1 -1
- package/dist/{index.native--Dm3KDDS.d.cts → index.native-DJlx-bfM.d.cts} +1 -1
- package/dist/index.native.cjs +1 -1
- package/dist/index.native.d.cts +5 -5
- package/dist/index.native.d.ts +5 -5
- package/dist/index.native.js +1 -1
- package/dist/tailwindPlugin.cjs +1 -1
- package/dist/tailwindPlugin.d.cts +1 -1
- package/dist/tailwindPlugin.d.ts +1 -1
- package/dist/tailwindPlugin.js +1 -1
- package/dist/tailwindPurge/utils.cjs +1 -0
- package/dist/tailwindPurge/utils.d.cts +25 -0
- package/dist/tailwindPurge/utils.d.ts +25 -0
- package/dist/tailwindPurge/utils.js +1 -0
- package/dist/tailwindPurge.cjs +1 -1
- package/dist/tailwindPurge.d.cts +5 -1
- package/dist/tailwindPurge.d.ts +5 -1
- package/dist/tailwindPurge.js +1 -1
- package/dist/tokens/index.cjs +1 -1
- package/dist/tokens/index.d.cts +2 -2
- package/dist/tokens/index.d.ts +2 -2
- package/dist/tokens/index.js +1 -1
- package/dist/tokens/index.native.cjs +1 -1
- package/dist/tokens/index.native.d.cts +2 -2
- package/dist/tokens/index.native.d.ts +2 -2
- package/dist/tokens/index.native.js +1 -1
- package/dist/tokens/parseTokens.cjs +1 -1
- package/dist/tokens/parseTokens.d.cts +1 -1
- package/dist/tokens/parseTokens.d.ts +1 -1
- package/dist/tokens/parseTokens.js +1 -1
- package/dist/tokens/parseTokens.native.d.cts +1 -1
- package/dist/tokens/parseTokens.native.d.ts +1 -1
- package/dist/{types-7oEBWtMQ.d.cts → types-CzJpH_Oi.d.cts} +2 -2
- package/dist/{types-7oEBWtMQ.d.ts → types-CzJpH_Oi.d.ts} +2 -2
- package/package.json +18 -2
- package/dist/Pressable-2kgXQNVs.d.cts +0 -55
- package/dist/Pressable-LIDkIIAF.d.ts +0 -55
- /package/dist/{types-J4DLS6Xj.d.cts → types-FO65RM-W.d.cts} +0 -0
- /package/dist/{types-J4DLS6Xj.d.ts → types-FO65RM-W.d.ts} +0 -0
package/cli/README.md
CHANGED
@@ -8,10 +8,10 @@ Bluebun relies on Bun APIs and is designed to be extremely fast, with no-depende
|
|
8
8
|
|
9
9
|
# Commands
|
10
10
|
|
11
|
-
In any consumer of `@yahoo/uds` the following commands are available:
|
12
|
-
|
13
11
|
> Please note: If you are _not_ running the CLI from a package.json script you will need to add `bun` before the binary in order to run it directly. i.e. `bun uds purge`
|
14
12
|
|
13
|
+
In any consumer of `@yahoo/uds` the following commands are available:
|
14
|
+
|
15
15
|
## Config
|
16
16
|
|
17
17
|
### Using args
|
@@ -23,9 +23,7 @@ In any consumer of `@yahoo/uds` the following commands are available:
|
|
23
23
|
|
24
24
|
```shell
|
25
25
|
uds sync --id [id] --outFile [path]
|
26
|
-
```
|
27
26
|
|
28
|
-
```shell
|
29
27
|
uds sync --id [id]
|
30
28
|
```
|
31
29
|
|
@@ -36,10 +34,6 @@ uds sync --id [id]
|
|
36
34
|
| UDS_ID | true | |
|
37
35
|
| UDS_OUT_FILE | false | ./uds.config.ts |
|
38
36
|
|
39
|
-
```shell
|
40
|
-
UDS_ID=[id] uds sync --outFile [path]
|
41
|
-
```
|
42
|
-
|
43
37
|
```shell
|
44
38
|
UDS_ID=[id] UDS_OUT_FILE=[path] uds sync
|
45
39
|
```
|
package/cli/bunfig.toml
ADDED
@@ -1,6 +1,7 @@
|
|
1
1
|
import fs from 'node:fs';
|
2
2
|
import os from 'node:os';
|
3
3
|
|
4
|
+
// @ts-expect-error this is a transitive dep of expo, which is required in consumers to even run this setup
|
4
5
|
import { EasJsonAccessor, EasJsonUtils, Platform } from '@expo/eas-json';
|
5
6
|
import { print, type Props } from 'bluebun';
|
6
7
|
import { $, semver, which } from 'bun';
|
@@ -4,7 +4,7 @@ import { type MobileProps, needsBinary, needsBrewFormula, setup } from './_setup
|
|
4
4
|
|
5
5
|
export default {
|
6
6
|
name: 'build',
|
7
|
-
description: '
|
7
|
+
description: '🏗️ Build',
|
8
8
|
run: async (props: MobileProps) => {
|
9
9
|
const { platform, profile, output } = await setup({
|
10
10
|
props,
|
package/cli/commands/purge.ts
CHANGED
package/cli/commands/sync.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import { Props } from 'bluebun';
|
1
|
+
import { magenta, print, Props } from 'bluebun';
|
2
2
|
|
3
3
|
import { setupConfigWorker } from '../utils/setupConfigWorker';
|
4
4
|
import { SyncOptions } from '../utils/types';
|
@@ -9,10 +9,22 @@ interface SyncProps extends Props {
|
|
9
9
|
|
10
10
|
export default {
|
11
11
|
name: 'sync',
|
12
|
-
description: '
|
13
|
-
|
12
|
+
description: '🔄 Update to latest design config 🎨',
|
13
|
+
alias: ['update'],
|
14
|
+
run: async ({ name, commandPath, options }: SyncProps) => {
|
15
|
+
const { id = Bun.env.UDS_ID, outFile = Bun.env.UDS_OUT_FILE } = options;
|
16
|
+
|
17
|
+
if (!id || typeof id === 'boolean') {
|
18
|
+
console.error(
|
19
|
+
'\nMissing config ID. Please pass an --id flag or set the UDS_ID env variable.\n',
|
20
|
+
);
|
21
|
+
print(`${magenta('Usage:')} ${name} ${commandPath} --id <config-id>\n`);
|
22
|
+
return;
|
23
|
+
}
|
24
|
+
|
14
25
|
await setupConfigWorker({
|
15
|
-
|
26
|
+
id,
|
27
|
+
outFile: outFile ?? './uds.config.ts',
|
16
28
|
onUpdate: ({ worker }) => {
|
17
29
|
if (!options.watch) {
|
18
30
|
worker.terminate();
|
package/cli/commands/uds.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import { type Props } from 'bluebun';
|
1
|
+
import { print, type Props, red } from 'bluebun';
|
2
2
|
|
3
3
|
import { getCommandHelp } from '../utils/getCommandHelp';
|
4
4
|
|
@@ -6,6 +6,9 @@ export default {
|
|
6
6
|
name: 'uds',
|
7
7
|
description: '',
|
8
8
|
run: async (props: Props) => {
|
9
|
+
if (props.first) {
|
10
|
+
print(red(`Unknown command: ${props.first}`));
|
11
|
+
}
|
9
12
|
await getCommandHelp(props);
|
10
13
|
},
|
11
14
|
};
|
package/cli/commands/version.ts
CHANGED
package/cli/preload.ts
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
import { jest, mock } from 'bun:test';
|
2
|
+
|
3
|
+
const mockFastGlob = jest.fn().mockResolvedValue(['/pages/PageA.tsx', '/pages/PageB.tsx']);
|
4
|
+
|
5
|
+
mock.module('fast-glob', () => ({ __esModule: true, default: mockFastGlob }));
|
6
|
+
|
7
|
+
mock.module('@yahoo/uds/tailwindPurge', () => ({
|
8
|
+
componentsDependencies: {
|
9
|
+
Button: ['Icon', 'Pressable', 'Text'],
|
10
|
+
Spinner: [],
|
11
|
+
HStack: ['Box'],
|
12
|
+
},
|
13
|
+
componentToVariants: {
|
14
|
+
Button: ['color'],
|
15
|
+
HStack: ['alignItems', 'justifyContent'],
|
16
|
+
Icon: ['color'],
|
17
|
+
Text: ['fontSize', 'fontFamily'],
|
18
|
+
Pressable: ['display'],
|
19
|
+
},
|
20
|
+
variantsList: ['color', 'alignItems', 'justifyContent', 'display', 'fontSize', 'fontFamily'],
|
21
|
+
variantToTailwindClass: {
|
22
|
+
color: 'text-accent text-alert text-black text-brand text-positive text-warning text-white',
|
23
|
+
alignItems: 'items-start items-end items-center items-stretch items-baseline',
|
24
|
+
justifyContent:
|
25
|
+
'justify-start justify-end justify-center justify-between justify-around justify-evenly',
|
26
|
+
display:
|
27
|
+
'block inline-block inline flex inline-flex table inline-table table-caption table-cell table-column table-column-group table-footer-group table-header-group table-row-group table-row flow-root grid contents',
|
28
|
+
fontSize: 'font-size-display1 font-size-title1 ',
|
29
|
+
fontFamily:
|
30
|
+
'font-icons font-sans font-sans-beta font-sans-condensed font-serif-text font-serif-display font-display1 font-title1 font-title2 font-title3 font-title4 font-headline1 font-body1 font-label1 font-label2 font-caption1 font-caption2 font-legal1',
|
31
|
+
},
|
32
|
+
componentToTwClasses: {
|
33
|
+
Box: 'flex',
|
34
|
+
HStack: 'container fill',
|
35
|
+
Icon: 'flex fill outline',
|
36
|
+
Image: 'flex',
|
37
|
+
Pressable: 'flex',
|
38
|
+
Text: 'flex text',
|
39
|
+
VStack: 'container fill',
|
40
|
+
Button: '',
|
41
|
+
},
|
42
|
+
}));
|
@@ -1,30 +1,49 @@
|
|
1
1
|
// prevents TS errors
|
2
2
|
declare var self: Worker;
|
3
3
|
|
4
|
-
import {
|
4
|
+
import { UniversalTokensConfig } from '@yahoo/uds/tokens';
|
5
5
|
|
6
6
|
import { ConfigWorkerThreadMessage } from './types';
|
7
7
|
|
8
|
+
const CLOUD_FUNCTION = 'https://syncconfig-j57v6zmjrq-uc.a.run.app';
|
9
|
+
|
8
10
|
self.onmessage = async ({ data }: ConfigWorkerThreadMessage) => {
|
11
|
+
const { id } = data.resp;
|
12
|
+
|
9
13
|
if (data.type === 'init') {
|
10
14
|
try {
|
11
|
-
//
|
15
|
+
// The firebase packaage available if we're in the UDS monorepo. Listen for updates.
|
12
16
|
const firebase = await import('database/firebase');
|
13
|
-
|
14
|
-
|
17
|
+
|
18
|
+
// Bail if the branch doesn't exist in configurator.
|
19
|
+
const branchExists = await firebase.branchExists(id);
|
20
|
+
if (!branchExists) {
|
21
|
+
throw new Error(`Config id '${id}' does not exist in the Configurator.`);
|
22
|
+
}
|
23
|
+
|
24
|
+
console.log('Fetching using local database package...');
|
25
|
+
|
26
|
+
firebase.onBranchSnapshot(id, ({ config, status }) => {
|
15
27
|
postMessage({ type: 'update', resp: { config, status } });
|
16
28
|
});
|
17
29
|
} catch (err) {
|
18
30
|
try {
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
throw Error;
|
31
|
+
console.log('Fetching from configurator...');
|
32
|
+
|
33
|
+
const resp = await fetch(`${CLOUD_FUNCTION}?id=${id}`);
|
34
|
+
if (!resp.ok) {
|
35
|
+
throw new Error(`Error fetching config id '${id}'. Does it exist in the Configurator?`);
|
25
36
|
}
|
37
|
+
|
38
|
+
const { config } = ((await resp.json()) as { config: UniversalTokensConfig }) ?? {};
|
39
|
+
if (!config) {
|
40
|
+
throw new Error('Config JSON could not be parsed.');
|
41
|
+
}
|
42
|
+
|
43
|
+
postMessage({ type: 'update', resp: { config, status: 'error' } });
|
26
44
|
} catch (err) {
|
27
|
-
|
45
|
+
console.error(`\n${(err as Error).message}\n`);
|
46
|
+
throw err;
|
28
47
|
}
|
29
48
|
}
|
30
49
|
}
|
@@ -1,21 +1,21 @@
|
|
1
1
|
import {
|
2
|
-
bold,
|
3
2
|
calcWidestCommandName,
|
4
|
-
CommandTree,
|
3
|
+
type CommandTree,
|
5
4
|
commandTree,
|
6
5
|
cyan,
|
7
6
|
gray,
|
7
|
+
green,
|
8
|
+
magenta,
|
8
9
|
print,
|
9
|
-
Props,
|
10
|
+
type Props,
|
10
11
|
white,
|
11
12
|
} from 'bluebun';
|
12
13
|
|
13
14
|
/**
|
14
|
-
*
|
15
15
|
* The formatting from bluebun for the help command is not great.
|
16
16
|
* I forked code from https://github.com/jamonholmgren/bluebun/blob/main/src/command-help.ts
|
17
17
|
*
|
18
|
-
* TODO:
|
18
|
+
* TODO: create helpers for better styling control for this & other features
|
19
19
|
*
|
20
20
|
* Some packages to checkout:
|
21
21
|
* - https://github.com/wobsoriano/blipgloss
|
@@ -29,7 +29,7 @@ async function formatHelp(initialProps: Props) {
|
|
29
29
|
const _tree = await commandTree(initialProps);
|
30
30
|
const tree = categoryToFilter ? { [categoryToFilter]: _tree[categoryToFilter] } : _tree;
|
31
31
|
|
32
|
-
const widest = calcWidestCommandName(tree) +
|
32
|
+
const widest = calcWidestCommandName(tree) + 10;
|
33
33
|
|
34
34
|
function generateHelp(cmdTree: CommandTree, prefix: string): string[] {
|
35
35
|
return Object.keys(cmdTree).flatMap((key) => {
|
@@ -49,16 +49,25 @@ async function formatHelp(initialProps: Props) {
|
|
49
49
|
|
50
50
|
const helpLines = generateHelp(tree, name);
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
52
|
+
// https://texteditor.com/multiline-text-art/
|
53
|
+
const banner = `
|
54
|
+
█ █ █▀▄ ▄▀▀ ▄▀▀ █ █
|
55
|
+
▀▄█ █▄▀ ▄██ ▀▄▄ █▄▄ █
|
56
|
+
Universal Design System
|
57
|
+
`.trim();
|
58
|
+
|
59
|
+
const help = `
|
60
|
+
${green(banner)}
|
61
|
+
|
62
|
+
${magenta(`Usage: ${white(`${name} <command>`)}`)}
|
63
|
+
|
64
|
+
${magenta('Commands:')}
|
65
|
+
${helpLines.join('\n')}
|
66
|
+
`;
|
56
67
|
|
57
68
|
return help;
|
58
69
|
}
|
59
70
|
|
60
71
|
export async function getCommandHelp(props: Props) {
|
61
|
-
print(
|
62
|
-
const helpText = await formatHelp(props);
|
63
|
-
print(helpText);
|
72
|
+
print(await formatHelp(props));
|
64
73
|
}
|
@@ -0,0 +1,139 @@
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
2
|
+
import { Project } from 'ts-morph';
|
3
|
+
|
4
|
+
import {
|
5
|
+
getComponentsToConvertToTW,
|
6
|
+
getFiles,
|
7
|
+
getTailwindSafelist,
|
8
|
+
getUsedProps,
|
9
|
+
isUDSComponent,
|
10
|
+
parseFiles,
|
11
|
+
} from './purgeCSS';
|
12
|
+
|
13
|
+
const PAGE_A_CODE = `
|
14
|
+
import { HStack, Button } from '@yahoo/uds';
|
15
|
+
|
16
|
+
const functionWithProp = () => {
|
17
|
+
const vars = {
|
18
|
+
isActive: false
|
19
|
+
};
|
20
|
+
|
21
|
+
return {
|
22
|
+
...vars,
|
23
|
+
color: 'blue'
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
const AnotherComponent = () => {
|
28
|
+
const propsList = functionWithProp();
|
29
|
+
return <HStack test="test" {...propsList}>meow</HStack>
|
30
|
+
}
|
31
|
+
|
32
|
+
const PageA = () => {
|
33
|
+
return (
|
34
|
+
<div>
|
35
|
+
<HStack />
|
36
|
+
<Button> Click me </Button>
|
37
|
+
</div>
|
38
|
+
)
|
39
|
+
}
|
40
|
+
`;
|
41
|
+
|
42
|
+
const PAGE_B_CODE = `
|
43
|
+
import { HStack, Spinner } from '@yahoo/uds';
|
44
|
+
import { noop } from 'lodash';
|
45
|
+
|
46
|
+
const PageB = () => {
|
47
|
+
return (
|
48
|
+
<div>
|
49
|
+
<HStack flexGrow="1" alignItems="center" justifyContent="center">
|
50
|
+
<Spinner />
|
51
|
+
</HStack>
|
52
|
+
</div>
|
53
|
+
)
|
54
|
+
}
|
55
|
+
`;
|
56
|
+
|
57
|
+
const FILES = ['/pages/PageA.tsx', '/pages/PageB.tsx'];
|
58
|
+
const IMPORTED_UDS_COMPONENTS = ['HStack', 'Button', 'Spinner'];
|
59
|
+
|
60
|
+
describe('purgeCSS', () => {
|
61
|
+
const project = new Project();
|
62
|
+
|
63
|
+
describe('getFiles', () => {
|
64
|
+
it('returns the list of files', async () => {
|
65
|
+
const files = await getFiles();
|
66
|
+
|
67
|
+
expect(files).toEqual(FILES);
|
68
|
+
});
|
69
|
+
});
|
70
|
+
|
71
|
+
describe('parseFiles', () => {
|
72
|
+
it('returns the list of imports from @yahoo/uds', () => {
|
73
|
+
project.createSourceFile(FILES[0], PAGE_A_CODE, {
|
74
|
+
overwrite: true,
|
75
|
+
});
|
76
|
+
project.createSourceFile(FILES[1], PAGE_B_CODE, {
|
77
|
+
overwrite: true,
|
78
|
+
});
|
79
|
+
|
80
|
+
const res = parseFiles(project, FILES);
|
81
|
+
|
82
|
+
expect(res).toEqual(IMPORTED_UDS_COMPONENTS);
|
83
|
+
});
|
84
|
+
});
|
85
|
+
|
86
|
+
describe('getTailwindSafelist', () => {
|
87
|
+
it('returns the tailwind classes corresponding to the props on a component', () => {
|
88
|
+
const res = getTailwindSafelist(project, IMPORTED_UDS_COMPONENTS);
|
89
|
+
|
90
|
+
expect(res).toEqual(
|
91
|
+
'container fill items-start items-end items-center items-stretch items-baseline justify-start justify-end justify-center justify-between justify-around justify-evenly text-accent text-alert text-black text-brand text-positive text-warning text-white ',
|
92
|
+
);
|
93
|
+
});
|
94
|
+
});
|
95
|
+
|
96
|
+
describe('getComponentsToConvertToTW', () => {
|
97
|
+
it('should get just exports which are components from UDS', () => {
|
98
|
+
const res = getComponentsToConvertToTW(['Button', 'HStack', 'randomThingy']);
|
99
|
+
|
100
|
+
expect(res).toEqual(['Button', 'Icon', 'Pressable', 'Text', 'HStack', 'Box']);
|
101
|
+
});
|
102
|
+
});
|
103
|
+
|
104
|
+
describe('isUDSComponent', () => {
|
105
|
+
it('returns true if the component is exported from UDS', () => {
|
106
|
+
const res = isUDSComponent('HStack');
|
107
|
+
|
108
|
+
expect(res).toBeTrue();
|
109
|
+
});
|
110
|
+
|
111
|
+
it('returns false if the component is not exported from UDS', () => {
|
112
|
+
const res = isUDSComponent('NotUdsComponent');
|
113
|
+
|
114
|
+
expect(res).toBeFalse();
|
115
|
+
});
|
116
|
+
});
|
117
|
+
|
118
|
+
describe('getUsedProps', () => {
|
119
|
+
it('return the list of all used props in the project for a given component', () => {
|
120
|
+
project.createSourceFile(FILES[0], PAGE_A_CODE, {
|
121
|
+
overwrite: true,
|
122
|
+
});
|
123
|
+
project.createSourceFile(FILES[1], PAGE_B_CODE, {
|
124
|
+
overwrite: true,
|
125
|
+
});
|
126
|
+
|
127
|
+
const usedProps = getUsedProps(project, 'HStack');
|
128
|
+
|
129
|
+
expect(usedProps).toEqual([
|
130
|
+
'test',
|
131
|
+
'color',
|
132
|
+
'isActive',
|
133
|
+
'flexGrow',
|
134
|
+
'alignItems',
|
135
|
+
'justifyContent',
|
136
|
+
]);
|
137
|
+
});
|
138
|
+
});
|
139
|
+
});
|
package/cli/utils/purgeCSS.ts
CHANGED
@@ -2,13 +2,18 @@ import path from 'node:path';
|
|
2
2
|
|
3
3
|
import {
|
4
4
|
componentsDependencies,
|
5
|
+
componentToTwClasses,
|
5
6
|
componentToVariants,
|
6
7
|
variantsList,
|
7
8
|
variantToTailwindClass,
|
8
9
|
} from '@yahoo/uds/tailwindPurge';
|
10
|
+
import {
|
11
|
+
findReferencesAsJsxElements,
|
12
|
+
getUsedPropsInReference,
|
13
|
+
} from '@yahoo/uds/tailwindPurge/utils';
|
9
14
|
import { spinStart } from 'bluebun';
|
10
15
|
import FastGlob from 'fast-glob';
|
11
|
-
import { Project } from 'ts-morph';
|
16
|
+
import { JsxOpeningElement, JsxSelfClosingElement, Project, ts } from 'ts-morph';
|
12
17
|
|
13
18
|
type SafeList = string;
|
14
19
|
type ImportsList = string[];
|
@@ -17,7 +22,7 @@ type Files = string[];
|
|
17
22
|
// TODO: use CLI args to power the output file path
|
18
23
|
const OUTPUT_FILE_PATH = Bun.file(`${Bun.env.PWD}/dist/safelist.js`);
|
19
24
|
|
20
|
-
const getFiles = async (): Promise<Files> => {
|
25
|
+
export const getFiles = async (): Promise<Files> => {
|
21
26
|
const workspaceDir = Bun.env.PWD;
|
22
27
|
const srcDir = path.join(workspaceDir, '/src/');
|
23
28
|
const files = await FastGlob(`${srcDir}/**/*.{jsx,tsx}`);
|
@@ -25,10 +30,41 @@ const getFiles = async (): Promise<Files> => {
|
|
25
30
|
return files;
|
26
31
|
};
|
27
32
|
|
33
|
+
/**
|
34
|
+
* Find all JSX references for a named import.
|
35
|
+
*
|
36
|
+
* @example
|
37
|
+
* const references = findNamedImportReferences(project, '@yahoo/uds', 'HStack')
|
38
|
+
*/
|
39
|
+
export function findNamedImportReferences(
|
40
|
+
project: Project,
|
41
|
+
moduleSpecifierValue: string,
|
42
|
+
namedImportName: string,
|
43
|
+
) {
|
44
|
+
const references: (JsxOpeningElement | JsxSelfClosingElement)[] = [];
|
45
|
+
for (const sourceFile of project.getSourceFiles()) {
|
46
|
+
for (const importDeclaration of sourceFile.getImportDeclarations()) {
|
47
|
+
if (importDeclaration.getModuleSpecifierValue() === moduleSpecifierValue) {
|
48
|
+
for (const namedImport of importDeclaration.getNamedImports()) {
|
49
|
+
if (namedImport.getName() === namedImportName) {
|
50
|
+
const identifier = namedImport.getFirstDescendantByKindOrThrow(
|
51
|
+
ts.SyntaxKind.Identifier,
|
52
|
+
);
|
53
|
+
|
54
|
+
references.push(...findReferencesAsJsxElements(identifier));
|
55
|
+
}
|
56
|
+
}
|
57
|
+
}
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
return references;
|
62
|
+
}
|
63
|
+
|
28
64
|
/**
|
29
65
|
* Given a file it returns the list of imports from @yahoo/uds
|
30
66
|
*/
|
31
|
-
const parseFiles = (project: Project, files: Files): ImportsList => {
|
67
|
+
export const parseFiles = (project: Project, files: Files): ImportsList => {
|
32
68
|
const importsSet = new Set();
|
33
69
|
|
34
70
|
const importsPerFile: string[] = files
|
@@ -59,27 +95,55 @@ const parseFiles = (project: Project, files: Files): ImportsList => {
|
|
59
95
|
return Array.from(importsSet) as string[];
|
60
96
|
};
|
61
97
|
|
62
|
-
const getTailwindSafelist = (componentList: string[]): SafeList => {
|
98
|
+
export const getTailwindSafelist = (project: Project, componentList: string[]): SafeList => {
|
99
|
+
let safeList: SafeList = '';
|
63
100
|
const validVariants = new Set<string>(variantsList);
|
64
101
|
const usedProps = new Set<string>();
|
65
102
|
componentList.forEach((component: string) => {
|
66
103
|
if (isUDSComponent(component)) {
|
104
|
+
// get the TW classes relevant for each prop
|
105
|
+
// these classes are used internally in UDS,
|
106
|
+
// they either have been initialized or used by other UDS components
|
67
107
|
componentToVariants[component].forEach((prop: string) => {
|
68
108
|
if (validVariants.has(prop) && !usedProps.has(prop)) {
|
69
109
|
usedProps.add(prop);
|
70
110
|
}
|
71
111
|
});
|
112
|
+
|
113
|
+
// scan the project for used props and
|
114
|
+
// get the corresponding css for those used props
|
115
|
+
getUsedProps(project, component).forEach((prop: string) => {
|
116
|
+
if (validVariants.has(prop) && !usedProps.has(prop)) {
|
117
|
+
usedProps.add(prop);
|
118
|
+
}
|
119
|
+
});
|
120
|
+
|
121
|
+
// get the inline TW classes used in each component
|
122
|
+
safeList += `${componentToTwClasses[component]} `;
|
72
123
|
}
|
73
124
|
});
|
74
125
|
|
75
|
-
let safeList = '';
|
76
126
|
for (const prop of usedProps) {
|
77
127
|
safeList += `${variantToTailwindClass[prop]} `;
|
78
128
|
}
|
79
129
|
return safeList;
|
80
130
|
};
|
81
131
|
|
82
|
-
|
132
|
+
/**
|
133
|
+
* Get the used props for a given component.
|
134
|
+
*
|
135
|
+
* @example
|
136
|
+
* const usedProps = getUsedProps(project, 'HStack');
|
137
|
+
*/
|
138
|
+
export const getUsedProps = (project: Project, component: string) => {
|
139
|
+
const references = findNamedImportReferences(project, '@yahoo/uds', component);
|
140
|
+
// for each reference find the used/references props
|
141
|
+
const usedProps = references.map((reference) => getUsedPropsInReference(reference)).flat();
|
142
|
+
|
143
|
+
return usedProps;
|
144
|
+
};
|
145
|
+
|
146
|
+
export const isUDSComponent = (component: string): boolean => {
|
83
147
|
return !!componentToVariants[component];
|
84
148
|
};
|
85
149
|
|
@@ -94,7 +158,7 @@ const saveToFile = async (safeList: SafeList) => {
|
|
94
158
|
await Bun.write(OUTPUT_FILE_PATH, fileContent);
|
95
159
|
};
|
96
160
|
|
97
|
-
const getComponentsToConvertToTW = (udsImport: ImportsList): string[] => {
|
161
|
+
export const getComponentsToConvertToTW = (udsImport: ImportsList): string[] => {
|
98
162
|
// filter out just the components
|
99
163
|
const components = udsImport.filter((importedItem) => !!componentToVariants[importedItem]);
|
100
164
|
const set = new Set();
|
@@ -128,7 +192,7 @@ async function purge() {
|
|
128
192
|
// 3. Now that we have the importer components
|
129
193
|
const udsComponents = getComponentsToConvertToTW(udsImports);
|
130
194
|
// 4. Generate the CSS we need
|
131
|
-
const safeList = getTailwindSafelist(udsComponents);
|
195
|
+
const safeList = getTailwindSafelist(project, udsComponents);
|
132
196
|
// 5. Write the allowlist to a file
|
133
197
|
await saveToFile(safeList);
|
134
198
|
}
|
@@ -2,52 +2,22 @@ import path from 'node:path';
|
|
2
2
|
|
3
3
|
import { sortKeys } from './sortKeys';
|
4
4
|
import { ConfigWorkerMainThreadMessage, SyncOptions } from './types';
|
5
|
-
|
6
|
-
const workerPath = path.resolve(import.meta.dir, './configWorker');
|
7
|
-
const workerURL = new URL(Bun.pathToFileURL(workerPath)).href;
|
8
|
-
|
9
5
|
interface ConfigWorkerOptions extends SyncOptions {
|
10
6
|
onUpdate?: (params: { worker: Worker }) => void;
|
11
7
|
}
|
12
8
|
|
13
|
-
export async function setupConfigWorker({
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
}: ConfigWorkerOptions = {}) {
|
18
|
-
const workspaceDir = Bun.env.PWD;
|
19
|
-
let configID = id;
|
20
|
-
let configOutFile = outFile ?? './uds.config.ts';
|
21
|
-
|
22
|
-
// If we didn't get the config ID from the command line or env vars, check for a uds.json file
|
23
|
-
if (!configID) {
|
24
|
-
const udsConfigFile = Bun.file(`${workspaceDir}/uds.json`);
|
25
|
-
const udsConfigFileExists = await udsConfigFile.exists();
|
26
|
-
if (udsConfigFileExists) {
|
27
|
-
const configJsonFile = (await udsConfigFile.json()) as { id: string; outFile: string };
|
28
|
-
|
29
|
-
configID = configJsonFile.id;
|
30
|
-
configOutFile = configJsonFile.outFile;
|
31
|
-
}
|
32
|
-
}
|
33
|
-
|
34
|
-
// have _nothing_, warn the eng to do something
|
35
|
-
if (!configID) {
|
36
|
-
console.error(
|
37
|
-
'\nMissing config ID. Please pass in --id, set UDS_ID in your .env, or create a uds.json file with { "id": "your-config-id" } defined.\n',
|
38
|
-
);
|
39
|
-
process.exit(1);
|
40
|
-
}
|
41
|
-
|
42
|
-
const outFilePath = `${workspaceDir}/${configOutFile}`;
|
9
|
+
export async function setupConfigWorker({ id, outFile, onUpdate }: ConfigWorkerOptions) {
|
10
|
+
const workerPath = path.resolve(import.meta.dir, './configWorker');
|
11
|
+
const workerURL = Bun.pathToFileURL(workerPath).href;
|
12
|
+
const outFilePath = `${Bun.env.PWD}/${outFile}`;
|
43
13
|
|
44
14
|
const worker = new Worker(workerURL);
|
45
15
|
|
46
16
|
worker.addEventListener('open', () => {
|
47
|
-
worker.postMessage({ type: 'init', resp: { id
|
17
|
+
worker.postMessage({ type: 'init', resp: { id } });
|
48
18
|
});
|
49
19
|
|
50
|
-
worker.
|
20
|
+
worker.addEventListener('message', async ({ data }: ConfigWorkerMainThreadMessage) => {
|
51
21
|
if (data.type === 'update') {
|
52
22
|
// Firebase does not guarantee the order of the keys in the config
|
53
23
|
// this can give us a false diff when writing to existing file
|
@@ -59,11 +29,11 @@ import { type UniversalTokensConfig } from '@yahoo/uds';
|
|
59
29
|
export const config: UniversalTokensConfig = ${JSON.stringify(sortedConfig, null, 2)};
|
60
30
|
`.trimStart();
|
61
31
|
|
62
|
-
console.log(`✅ Synced UDS config ${
|
32
|
+
console.log(`✅ Synced UDS config ${id} to ${outFilePath}`);
|
63
33
|
await Bun.write(outFilePath, configContent);
|
64
34
|
onUpdate?.({ worker });
|
65
35
|
}
|
66
|
-
};
|
36
|
+
});
|
67
37
|
|
68
38
|
// Register a handler for the SIGINT signal (Ctrl + C)
|
69
39
|
process.on('SIGINT', () => {
|
package/cli/utils/types.ts
CHANGED
@@ -7,7 +7,10 @@ export type ConfigWorkerMainThreadMessage = MessageEvent<{
|
|
7
7
|
}>;
|
8
8
|
|
9
9
|
export type SyncOptions = {
|
10
|
-
id
|
11
|
-
|
10
|
+
/** Configurator (branch) id */
|
11
|
+
id: string;
|
12
|
+
/** The file to write the config to. Defaults to uds.config.ts. */
|
13
|
+
outFile: string;
|
14
|
+
/** Enable watch mode? */
|
12
15
|
watch?: boolean;
|
13
16
|
};
|