@yahoo/uds 0.2.0 → 0.3.0

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 (75) hide show
  1. package/cli/README.md +2 -8
  2. package/cli/bunfig.toml +3 -0
  3. package/cli/commands/expo/_setup.ts +1 -0
  4. package/cli/commands/expo/build.ts +1 -1
  5. package/cli/commands/expo/launch.ts +1 -1
  6. package/cli/commands/purge.ts +1 -1
  7. package/cli/commands/sync.ts +16 -4
  8. package/cli/commands/uds.ts +4 -1
  9. package/cli/commands/version.ts +1 -1
  10. package/cli/preload.ts +42 -0
  11. package/cli/utils/configWorker.ts +30 -11
  12. package/cli/utils/getCommandHelp.ts +22 -13
  13. package/cli/utils/purgeCSS.test.ts +139 -0
  14. package/cli/utils/purgeCSS.ts +72 -8
  15. package/cli/utils/setupConfigWorker.ts +8 -38
  16. package/cli/utils/types.ts +5 -2
  17. package/dist/{Image.native-tkOXN29I.d.ts → Image.native-C6kOWgnf.d.ts} +1 -1
  18. package/dist/{Image.native-jCNIrPZD.d.cts → Image.native-VeXt5aeI.d.cts} +1 -1
  19. package/dist/VStack-BSD9TbBd.d.cts +114 -0
  20. package/dist/VStack-Dk3-8IyU.d.ts +114 -0
  21. package/dist/experimental/index.cjs +1 -1
  22. package/dist/experimental/index.d.cts +5 -4
  23. package/dist/experimental/index.d.ts +5 -4
  24. package/dist/experimental/index.js +1 -1
  25. package/dist/experimental/index.native.cjs +1 -1
  26. package/dist/experimental/index.native.d.cts +3 -3
  27. package/dist/experimental/index.native.d.ts +3 -3
  28. package/dist/experimental/index.native.js +1 -1
  29. package/dist/fixtures/index.cjs +1 -1
  30. package/dist/fixtures/index.d.cts +15 -1
  31. package/dist/fixtures/index.d.ts +15 -1
  32. package/dist/fixtures/index.js +1 -1
  33. package/dist/index.cjs +1 -1
  34. package/dist/index.d.cts +133 -184
  35. package/dist/index.d.ts +133 -184
  36. package/dist/index.js +1 -1
  37. package/dist/{index.native-xVHqKK0u.d.ts → index.native-CisPq4BI.d.ts} +1 -1
  38. package/dist/{index.native--Dm3KDDS.d.cts → index.native-DJlx-bfM.d.cts} +1 -1
  39. package/dist/index.native.cjs +1 -1
  40. package/dist/index.native.d.cts +5 -5
  41. package/dist/index.native.d.ts +5 -5
  42. package/dist/index.native.js +1 -1
  43. package/dist/tailwindPlugin.cjs +1 -1
  44. package/dist/tailwindPlugin.d.cts +1 -1
  45. package/dist/tailwindPlugin.d.ts +1 -1
  46. package/dist/tailwindPlugin.js +1 -1
  47. package/dist/tailwindPurge/utils.cjs +1 -0
  48. package/dist/tailwindPurge/utils.d.cts +25 -0
  49. package/dist/tailwindPurge/utils.d.ts +25 -0
  50. package/dist/tailwindPurge/utils.js +1 -0
  51. package/dist/tailwindPurge.cjs +1 -1
  52. package/dist/tailwindPurge.d.cts +5 -1
  53. package/dist/tailwindPurge.d.ts +5 -1
  54. package/dist/tailwindPurge.js +1 -1
  55. package/dist/tokens/index.cjs +1 -1
  56. package/dist/tokens/index.d.cts +2 -2
  57. package/dist/tokens/index.d.ts +2 -2
  58. package/dist/tokens/index.js +1 -1
  59. package/dist/tokens/index.native.cjs +1 -1
  60. package/dist/tokens/index.native.d.cts +2 -2
  61. package/dist/tokens/index.native.d.ts +2 -2
  62. package/dist/tokens/index.native.js +1 -1
  63. package/dist/tokens/parseTokens.cjs +1 -1
  64. package/dist/tokens/parseTokens.d.cts +1 -1
  65. package/dist/tokens/parseTokens.d.ts +1 -1
  66. package/dist/tokens/parseTokens.js +1 -1
  67. package/dist/tokens/parseTokens.native.d.cts +1 -1
  68. package/dist/tokens/parseTokens.native.d.ts +1 -1
  69. package/dist/{types-7oEBWtMQ.d.cts → types-CzJpH_Oi.d.cts} +2 -2
  70. package/dist/{types-7oEBWtMQ.d.ts → types-CzJpH_Oi.d.ts} +2 -2
  71. package/package.json +18 -2
  72. package/dist/Pressable-2kgXQNVs.d.cts +0 -55
  73. package/dist/Pressable-LIDkIIAF.d.ts +0 -55
  74. /package/dist/{types-J4DLS6Xj.d.cts → types-FO65RM-W.d.cts} +0 -0
  75. /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
  ```
@@ -0,0 +1,3 @@
1
+ [test]
2
+ # Load these modules before running tests.
3
+ preload = ["./preload"]
@@ -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: '🚀 Build',
7
+ description: '🏗️ Build',
8
8
  run: async (props: MobileProps) => {
9
9
  const { platform, profile, output } = await setup({
10
10
  props,
@@ -5,7 +5,7 @@ import { setup } from './_setup';
5
5
 
6
6
  export default {
7
7
  name: 'launch',
8
- description: '🚀 Launch',
8
+ description: '📱 Launch',
9
9
  run: async (props: MobileProps) => {
10
10
  const { platform, output } = await setup({
11
11
  props,
@@ -11,6 +11,6 @@ export default {
11
11
 
12
12
  await purge();
13
13
 
14
- spinStop('✅ Purging css done!');
14
+ spinStop('✅', 'Purging css done!');
15
15
  },
16
16
  };
@@ -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: '🌈 Sync',
13
- run: async ({ options }: SyncProps) => {
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
- ...options,
26
+ id,
27
+ outFile: outFile ?? './uds.config.ts',
16
28
  onUpdate: ({ worker }) => {
17
29
  if (!options.watch) {
18
30
  worker.terminate();
@@ -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
  };
@@ -4,7 +4,7 @@ import packageJson from '../../package.json';
4
4
 
5
5
  export default {
6
6
  name: 'version',
7
- description: `💾 ${packageJson.version}`,
7
+ description: `${packageJson.version}`,
8
8
  run: async () => {
9
9
  print(packageJson.version);
10
10
  },
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 { $ } from 'bun';
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
- // Only listen for updates if firebase is available. This will only be available in UDS monorepo atm
15
+ // The firebase packaage available if we're in the UDS monorepo. Listen for updates.
12
16
  const firebase = await import('database/firebase');
13
- console.write('Fetching from local database package...\n');
14
- firebase.onBranchSnapshot(data.resp.id, ({ config, status }) => {
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
- const res =
20
- await $`curl https://syncconfig-j57v6zmjrq-uc.a.run.app?id=${data.resp.id}`.json();
21
- if (res?.config) {
22
- postMessage({ type: 'update', resp: { config: res.config, status: 'error' } });
23
- } else {
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
- throw new Error('Error fetching config');
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: Maybe create helpers for better styling control for this & other features
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) + 5;
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
- const help = `${bold(white('Commands:'))}
53
-
54
- ${helpLines.join('\n')}
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
+ });
@@ -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
- const isUDSComponent = (component: string): boolean => {
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
- id = Bun.env.UDS_ID,
15
- outFile = Bun.env.UDS_OUT_FILE,
16
- onUpdate,
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: configID } });
17
+ worker.postMessage({ type: 'init', resp: { id } });
48
18
  });
49
19
 
50
- worker.onmessage = async ({ data }: ConfigWorkerMainThreadMessage) => {
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 ${configID} to ${outFilePath}`);
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', () => {
@@ -7,7 +7,10 @@ export type ConfigWorkerMainThreadMessage = MessageEvent<{
7
7
  }>;
8
8
 
9
9
  export type SyncOptions = {
10
- id?: string;
11
- outFile?: string;
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
  };