react-email 4.0.0-alpha.5 → 4.0.0-alpha.7

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 (203) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/cli/index.js +1179 -2659
  3. package/dist/cli/index.mjs +17 -11
  4. package/dist/preview/.next/BUILD_ID +1 -1
  5. package/dist/preview/.next/app-build-manifest.json +32 -31
  6. package/dist/preview/.next/app-path-routes-manifest.json +6 -1
  7. package/dist/preview/.next/build-manifest.json +14 -14
  8. package/dist/preview/.next/cache/.rscinfo +1 -1
  9. package/dist/preview/.next/cache/webpack/client-production/0.pack +0 -0
  10. package/dist/preview/.next/cache/webpack/client-production/index.pack +0 -0
  11. package/dist/preview/.next/cache/webpack/edge-server-production/index.pack +0 -0
  12. package/dist/preview/.next/cache/webpack/server-production/0.pack +0 -0
  13. package/dist/preview/.next/cache/webpack/server-production/index.pack +0 -0
  14. package/dist/preview/.next/diagnostics/framework.json +1 -1
  15. package/dist/preview/.next/export-marker.json +6 -1
  16. package/dist/preview/.next/images-manifest.json +57 -1
  17. package/dist/preview/.next/next-minimal-server.js.nft.json +1 -1
  18. package/dist/preview/.next/next-server.js.nft.json +1 -1
  19. package/dist/preview/.next/prerender-manifest.json +41 -1
  20. package/dist/preview/.next/required-server-files.json +310 -1
  21. package/dist/preview/.next/routes-manifest.json +64 -1
  22. package/dist/preview/.next/server/app/_not-found/page.js +1 -1
  23. package/dist/preview/.next/server/app/_not-found/page.js.nft.json +1 -1
  24. package/dist/preview/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  25. package/dist/preview/.next/server/app/favicon.ico/route.js +1 -1
  26. package/dist/preview/.next/server/app/favicon.ico/route.js.nft.json +1 -1
  27. package/dist/preview/.next/server/app/page.js +1 -1
  28. package/dist/preview/.next/server/app/page.js.nft.json +1 -1
  29. package/dist/preview/.next/server/app/page_client-reference-manifest.js +1 -1
  30. package/dist/preview/.next/server/app/preview/[...slug]/page.js +51 -11
  31. package/dist/preview/.next/server/app/preview/[...slug]/page.js.nft.json +1 -1
  32. package/dist/preview/.next/server/app/preview/[...slug]/page_client-reference-manifest.js +1 -1
  33. package/dist/preview/.next/server/app-paths-manifest.json +1 -1
  34. package/dist/preview/.next/server/chunks/446.js +6 -0
  35. package/dist/preview/.next/server/chunks/600.js +8 -0
  36. package/dist/preview/.next/server/chunks/811.js +13 -0
  37. package/dist/preview/.next/server/chunks/816.js +14 -0
  38. package/dist/preview/.next/server/chunks/943.js +1 -0
  39. package/dist/preview/.next/server/functions-config-manifest.json +4 -1
  40. package/dist/preview/.next/server/middleware-build-manifest.js +1 -1
  41. package/dist/preview/.next/server/next-font-manifest.js +1 -1
  42. package/dist/preview/.next/server/next-font-manifest.json +1 -1
  43. package/dist/preview/.next/server/pages/500.html +1 -1
  44. package/dist/preview/.next/server/pages/_app.js +1 -1
  45. package/dist/preview/.next/server/pages/_app.js.nft.json +1 -1
  46. package/dist/preview/.next/server/pages/_document.js +1 -1
  47. package/dist/preview/.next/server/pages/_document.js.nft.json +1 -1
  48. package/dist/preview/.next/server/pages/_error.js +1 -1
  49. package/dist/preview/.next/server/pages/_error.js.nft.json +1 -1
  50. package/dist/preview/.next/server/pages-manifest.json +5 -1
  51. package/dist/preview/.next/server/server-reference-manifest.js +1 -1
  52. package/dist/preview/.next/server/server-reference-manifest.json +1 -1
  53. package/dist/preview/.next/server/webpack-runtime.js +1 -1
  54. package/dist/preview/.next/static/Pms2orsQgT5xpttCfZfH5/_buildManifest.js +1 -0
  55. package/dist/preview/.next/static/chunks/287-7864b805e6bdc854.js +1 -0
  56. package/dist/preview/.next/static/chunks/412-31817e53b50a3e73.js +1 -0
  57. package/dist/preview/.next/static/chunks/683-1fb40795502f6e63.js +1 -0
  58. package/dist/preview/.next/static/chunks/744-79730358b37b2212.js +1 -0
  59. package/dist/preview/.next/static/chunks/781-5f16c6bc9d9d4cc1.js +1 -0
  60. package/dist/preview/.next/static/chunks/832ad4be-cb988facfb8f955f.js +1 -0
  61. package/dist/preview/.next/static/chunks/880-9c0b721328117b8b.js +1 -0
  62. package/dist/preview/.next/static/chunks/{afa401a5-a600c227dacf3ab4.js → afa401a5-3e949a1cfd317dd3.js} +3 -3
  63. package/dist/preview/.next/static/chunks/app/_not-found/page-09d694081cc9d4dc.js +1 -0
  64. package/dist/preview/.next/static/chunks/app/layout-ffdee5cc1be30e7b.js +1 -0
  65. package/dist/preview/.next/static/chunks/app/page-9ea0bd45cd6294b0.js +1 -0
  66. package/dist/preview/.next/static/chunks/app/preview/[...slug]/page-9e22979a25c836c0.js +1 -0
  67. package/dist/preview/.next/static/chunks/framework-c2bd6d936e3077bc.js +1 -0
  68. package/dist/preview/.next/static/chunks/main-44463a8301435b64.js +1 -0
  69. package/dist/preview/.next/static/chunks/main-app-256b213b179a95cc.js +1 -0
  70. package/dist/preview/.next/static/chunks/pages/_app-f3011d3f00bb8dba.js +1 -0
  71. package/dist/preview/.next/static/chunks/pages/_error-39a87dee2e97a2a3.js +1 -0
  72. package/dist/preview/.next/static/chunks/{webpack-2eb145a20ee6cb77.js → webpack-41e2667c9f086a4f.js} +1 -1
  73. package/dist/preview/.next/static/css/eaae8ce545b295f9.css +3 -0
  74. package/dist/preview/.next/trace +26 -22
  75. package/dist/preview/.next/types/app/layout.ts +1 -1
  76. package/dist/preview/.next/types/app/page.ts +84 -0
  77. package/dist/preview/.next/types/app/preview/[...slug]/page.ts +1 -1
  78. package/dist/preview/.next/types/cache-life.d.ts +3 -3
  79. package/package.json +14 -9
  80. package/scripts/build-preview-server.mjs +32 -0
  81. package/scripts/fill-caniemail-data.mjs +36 -0
  82. package/src/actions/email-validation/caniemail-data.ts +85993 -0
  83. package/src/actions/email-validation/check-compatibility.ts +321 -0
  84. package/src/actions/email-validation/check-images.spec.tsx +15 -13
  85. package/src/actions/email-validation/check-images.ts +8 -2
  86. package/src/actions/email-validation/check-links.spec.tsx +27 -15
  87. package/src/actions/email-validation/check-links.ts +8 -2
  88. package/src/actions/email-validation/get-code-location-from-ast-element.ts +18 -0
  89. package/src/actions/get-email-path-from-slug.ts +1 -1
  90. package/src/actions/render-email-by-path.tsx +2 -1
  91. package/src/{utils/emails-directory-absolute-path.ts → app/env.ts} +5 -0
  92. package/src/app/layout.tsx +1 -1
  93. package/src/app/page.tsx +1 -1
  94. package/src/app/preview/[...slug]/page.tsx +89 -19
  95. package/src/app/preview/[...slug]/preview.tsx +25 -68
  96. package/src/components/code-container.tsx +90 -71
  97. package/src/components/code.tsx +106 -43
  98. package/src/components/icons/icon-info.tsx +18 -0
  99. package/src/components/icons/icon-reload.tsx +13 -14
  100. package/src/components/logo.tsx +3 -2
  101. package/src/components/resizable-wrapper.tsx +1 -4
  102. package/src/components/sidebar/file-tree-directory-children.tsx +1 -0
  103. package/src/components/sidebar/sidebar.tsx +2 -3
  104. package/src/components/toolbar/code-preview-line-link.tsx +40 -0
  105. package/src/components/toolbar/compatibility.tsx +113 -0
  106. package/src/components/toolbar/linter.tsx +226 -125
  107. package/src/components/toolbar/results.tsx +5 -2
  108. package/src/components/toolbar/spam-assassin.tsx +40 -43
  109. package/src/components/toolbar/toolbar-button.tsx +52 -0
  110. package/src/components/toolbar/use-cached-state.ts +33 -0
  111. package/src/components/toolbar.tsx +196 -110
  112. package/src/components/tooltip-content.tsx +1 -1
  113. package/src/components/topbar/view-size-controls.tsx +1 -1
  114. package/src/components/topbar.tsx +4 -29
  115. package/src/contexts/emails.tsx +2 -1
  116. package/src/contexts/fragment-identifier.tsx +46 -0
  117. package/src/contexts/preview.tsx +81 -0
  118. package/src/hooks/use-email-rendering-result.ts +2 -1
  119. package/src/hooks/use-fragment-identifier.ts +14 -0
  120. package/src/utils/__snapshots__/get-email-component.spec.ts.snap +1 -1
  121. package/src/utils/caniemail/all-css-properties.ts +358 -0
  122. package/src/utils/caniemail/ast/get-object-variables.ts +61 -0
  123. package/src/utils/caniemail/ast/get-used-style-properties.ts +91 -0
  124. package/src/utils/caniemail/get-compatibility-stats-for-entry.ts +118 -0
  125. package/src/utils/caniemail/get-css-functions.ts +25 -0
  126. package/src/utils/caniemail/get-css-property-names.ts +32 -0
  127. package/src/utils/caniemail/get-css-property-with-value.ts +14 -0
  128. package/src/utils/caniemail/get-css-unit.ts +3 -0
  129. package/src/utils/caniemail/get-element-attributes.ts +7 -0
  130. package/src/utils/caniemail/get-element-names.ts +20 -0
  131. package/src/utils/caniemail/tailwind/generate-tailwind-rules.ts +30 -0
  132. package/src/utils/caniemail/tailwind/get-tailwind-config.ts +203 -0
  133. package/src/utils/caniemail/tailwind/get-tailwind-metadata.spec.ts +25 -0
  134. package/src/utils/caniemail/tailwind/get-tailwind-metadata.ts +45 -0
  135. package/src/utils/caniemail/tailwind/setup-tailwind-context.ts +15 -0
  136. package/src/utils/get-email-component.ts +34 -67
  137. package/src/utils/get-line-and-column-from-offset.spec.ts +11 -0
  138. package/src/utils/get-line-and-column-from-offset.ts +11 -0
  139. package/src/utils/index.ts +1 -0
  140. package/src/utils/linting.ts +60 -0
  141. package/src/utils/load-stream.ts +15 -0
  142. package/src/utils/result.ts +49 -0
  143. package/src/utils/run-bundled-code.ts +64 -0
  144. package/src/utils/sanitize.ts +6 -0
  145. package/tailwind-internals.d.ts +133 -0
  146. package/tsconfig.json +9 -3
  147. package/build-preview-server.mjs +0 -25
  148. package/dist/preview/.next/cache/images/TcyzHbFXGFjrOu3wEMvDoSmqCh3qP3iiNqJf0QbED9Y/60.1741728556140.cQ5qicbpvoXZ7leVmWqG2ElLwXB1ynYeSv8MBSA-QeM.Vy8iMWM3MGUtMTk1ODcxYmIyNzMi.webp +0 -0
  149. package/dist/preview/.next/cache/webpack/client-development/0.pack.gz +0 -0
  150. package/dist/preview/.next/cache/webpack/client-development/1.pack.gz +0 -0
  151. package/dist/preview/.next/cache/webpack/client-development/10.pack.gz +0 -0
  152. package/dist/preview/.next/cache/webpack/client-development/11.pack.gz +0 -0
  153. package/dist/preview/.next/cache/webpack/client-development/12.pack.gz +0 -0
  154. package/dist/preview/.next/cache/webpack/client-development/13.pack.gz +0 -0
  155. package/dist/preview/.next/cache/webpack/client-development/2.pack.gz +0 -0
  156. package/dist/preview/.next/cache/webpack/client-development/3.pack.gz +0 -0
  157. package/dist/preview/.next/cache/webpack/client-development/4.pack.gz +0 -0
  158. package/dist/preview/.next/cache/webpack/client-development/5.pack.gz +0 -0
  159. package/dist/preview/.next/cache/webpack/client-development/6.pack.gz +0 -0
  160. package/dist/preview/.next/cache/webpack/client-development/7.pack.gz +0 -0
  161. package/dist/preview/.next/cache/webpack/client-development/8.pack.gz +0 -0
  162. package/dist/preview/.next/cache/webpack/client-development/9.pack.gz +0 -0
  163. package/dist/preview/.next/cache/webpack/client-development/index.pack.gz +0 -0
  164. package/dist/preview/.next/cache/webpack/client-development/index.pack.gz.old +0 -0
  165. package/dist/preview/.next/cache/webpack/server-development/0.pack.gz +0 -0
  166. package/dist/preview/.next/cache/webpack/server-development/1.pack.gz +0 -0
  167. package/dist/preview/.next/cache/webpack/server-development/2.pack.gz +0 -0
  168. package/dist/preview/.next/cache/webpack/server-development/3.pack.gz +0 -0
  169. package/dist/preview/.next/cache/webpack/server-development/4.pack.gz +0 -0
  170. package/dist/preview/.next/cache/webpack/server-development/5.pack.gz +0 -0
  171. package/dist/preview/.next/cache/webpack/server-development/6.pack.gz +0 -0
  172. package/dist/preview/.next/cache/webpack/server-development/7.pack.gz +0 -0
  173. package/dist/preview/.next/cache/webpack/server-development/8.pack.gz +0 -0
  174. package/dist/preview/.next/cache/webpack/server-development/9.pack.gz +0 -0
  175. package/dist/preview/.next/cache/webpack/server-development/index.pack.gz +0 -0
  176. package/dist/preview/.next/cache/webpack/server-development/index.pack.gz.old +0 -0
  177. package/dist/preview/.next/server/chunks/143.js +0 -6
  178. package/dist/preview/.next/server/chunks/409.js +0 -5
  179. package/dist/preview/.next/server/chunks/46.js +0 -1
  180. package/dist/preview/.next/server/chunks/478.js +0 -14
  181. package/dist/preview/.next/server/chunks/707.js +0 -13
  182. package/dist/preview/.next/static/B4EYZiVzdylEG9lAIl-aO/_buildManifest.js +0 -1
  183. package/dist/preview/.next/static/chunks/575-bc52750855c25df4.js +0 -2
  184. package/dist/preview/.next/static/chunks/684-0f1ef7361c499798.js +0 -1
  185. package/dist/preview/.next/static/chunks/684c6b30-0c65da32762fc4ee.js +0 -1
  186. package/dist/preview/.next/static/chunks/81-e7539b08d9d3fb4d.js +0 -1
  187. package/dist/preview/.next/static/chunks/883-70c8267c50bc4133.js +0 -1
  188. package/dist/preview/.next/static/chunks/921-d1dc8c63f49e85d6.js +0 -1
  189. package/dist/preview/.next/static/chunks/app/_not-found/page-03ce767859c36d4e.js +0 -1
  190. package/dist/preview/.next/static/chunks/app/layout-7cf14e28880544f1.js +0 -1
  191. package/dist/preview/.next/static/chunks/app/page-065cb49b0a078541.js +0 -1
  192. package/dist/preview/.next/static/chunks/app/preview/[...slug]/page-656510fd180c803c.js +0 -1
  193. package/dist/preview/.next/static/chunks/framework-2a724981073c3a29.js +0 -1
  194. package/dist/preview/.next/static/chunks/main-552b9719bbc3a274.js +0 -1
  195. package/dist/preview/.next/static/chunks/main-app-914a73336fd45af5.js +0 -1
  196. package/dist/preview/.next/static/chunks/pages/_app-77ca34bce25ac75c.js +0 -1
  197. package/dist/preview/.next/static/chunks/pages/_error-73f611c46abbb495.js +0 -1
  198. package/dist/preview/.next/static/css/2df96d9ee014e8de.css +0 -3
  199. package/src/actions/email-validation/get-line-and-column-from-index.spec.ts +0 -22
  200. package/src/actions/email-validation/get-line-and-column-from-index.ts +0 -43
  201. package/src/components/icons/icon-scanner.tsx +0 -19
  202. package/src/components/icons/icon-scissors.tsx +0 -19
  203. /package/dist/preview/.next/static/{B4EYZiVzdylEG9lAIl-aO → Pms2orsQgT5xpttCfZfH5}/_ssgManifest.js +0 -0
@@ -0,0 +1,15 @@
1
+ import { createContext } from 'tailwindcss/lib/lib/setupContextUtils';
2
+ import resolveConfig from 'tailwindcss/resolveConfig';
3
+ import type { TailwindConfig } from './get-tailwind-config';
4
+
5
+ export const setupTailwindContext = (config: TailwindConfig) => {
6
+ return createContext(
7
+ resolveConfig({
8
+ ...config,
9
+ content: [],
10
+ corePlugins: {
11
+ preflight: false,
12
+ },
13
+ }),
14
+ );
15
+ };
@@ -1,16 +1,22 @@
1
- /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
1
  import path from 'node:path';
3
- import vm from 'node:vm';
4
2
  import type { render } from '@react-email/render';
5
3
  import { type BuildFailure, type OutputFile, build } from 'esbuild';
6
4
  import type React from 'react';
7
5
  import type { RawSourceMap } from 'source-map-js';
6
+ import { z } from 'zod';
8
7
  import { renderingUtilitiesExporter } from './esbuild/renderring-utilities-exporter';
9
8
  import { improveErrorWithSourceMap } from './improve-error-with-sourcemap';
10
- import { staticNodeModulesForVM } from './static-node-modules-for-vm';
9
+ import { isErr } from './result';
10
+ import { runBundledCode } from './run-bundled-code';
11
11
  import type { EmailTemplate as EmailComponent } from './types/email-template';
12
12
  import type { ErrorObject } from './types/error-object';
13
13
 
14
+ const EmailComponentModule = z.object({
15
+ default: z.any(),
16
+ render: z.function(),
17
+ reactEmailCreateReactElement: z.function(),
18
+ });
19
+
14
20
  export const getEmailComponent = async (
15
21
  emailPath: string,
16
22
  ): Promise<
@@ -61,79 +67,38 @@ export const getEmailComponent = async (
61
67
  const bundledEmailFile = outputFiles[1]!;
62
68
  const builtEmailCode = bundledEmailFile.text;
63
69
 
64
- const fakeContext = {
65
- ...global,
66
- console,
67
- Buffer,
68
- AbortSignal,
69
- Event,
70
- EventTarget,
71
- TextDecoder,
72
- Request,
73
- Response,
74
- TextDecoderStream,
75
- TextEncoder,
76
- TextEncoderStream,
77
- ReadableStream,
78
- URL,
79
- URLSearchParams,
80
- Headers,
81
- module: {
82
- exports: {
83
- default: undefined as unknown,
84
- render: undefined as unknown,
85
- reactEmailCreateReactElement: undefined as unknown,
86
- },
87
- },
88
- __filename: emailPath,
89
- __dirname: path.dirname(emailPath),
90
- require: (specifiedModule: string) => {
91
- let m = specifiedModule;
92
- if (specifiedModule.startsWith('node:')) {
93
- m = m.split(':')[1]!;
94
- }
95
-
96
- if (m in staticNodeModulesForVM) {
97
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
98
- return staticNodeModulesForVM[m];
99
- }
100
-
101
- // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-useless-template-literals
102
- return require(`${specifiedModule}`) as unknown;
103
- // this stupid string templating was necessary to not have
104
- // webpack warnings like:
105
- //
106
- // Import trace for requested module:
107
- // ./src/utils/get-email-component.tsx
108
- // ./src/app/page.tsx
109
- // ⚠ ./src/utils/get-email-component.tsx
110
- // Critical dependency: the request of a dependency is an expression
111
- },
112
- process,
113
- };
114
70
  const sourceMapToEmail = JSON.parse(sourceMapFile.text) as RawSourceMap;
115
71
  // because it will have a path like <tsconfigLocation>/stdout/email.js.map
116
72
  sourceMapToEmail.sourceRoot = path.resolve(sourceMapFile.path, '../..');
117
73
  sourceMapToEmail.sources = sourceMapToEmail.sources.map((source) =>
118
74
  path.resolve(sourceMapFile.path, '..', source),
119
75
  );
120
- try {
121
- vm.runInNewContext(builtEmailCode, fakeContext, { filename: emailPath });
122
- } catch (exception) {
123
- const error = exception as Error;
124
76
 
125
- error.stack &&= error.stack.split('at Script.runInContext (node:vm')[0];
77
+ const runningResult = runBundledCode(builtEmailCode, emailPath);
126
78
 
127
- return {
128
- error: improveErrorWithSourceMap(error, emailPath, sourceMapToEmail),
129
- };
79
+ if (isErr(runningResult)) {
80
+ const { error } = runningResult;
81
+ if (error instanceof Error) {
82
+ error.stack &&= error.stack.split('at Script.runInContext (node:vm')[0];
83
+
84
+ return {
85
+ error: improveErrorWithSourceMap(error, emailPath, sourceMapToEmail),
86
+ };
87
+ }
88
+
89
+ throw error;
130
90
  }
131
91
 
132
- if (fakeContext.module.exports.default === undefined) {
92
+ const parseResult = EmailComponentModule.safeParse(runningResult.value);
93
+
94
+ if (parseResult.error) {
133
95
  return {
134
96
  error: improveErrorWithSourceMap(
135
97
  new Error(
136
- `The email component at ${emailPath} does not contain a default export`,
98
+ `The email component at ${emailPath} does not contain the expected exports`,
99
+ {
100
+ cause: parseResult.error,
101
+ },
137
102
  ),
138
103
  emailPath,
139
104
  sourceMapToEmail,
@@ -141,11 +106,13 @@ export const getEmailComponent = async (
141
106
  };
142
107
  }
143
108
 
109
+ const { data: componentModule } = parseResult;
110
+
144
111
  return {
145
- emailComponent: fakeContext.module.exports.default as EmailComponent,
146
- render: fakeContext.module.exports.render as typeof render,
147
- createElement: fakeContext.module.exports
148
- .reactEmailCreateReactElement as typeof React.createElement,
112
+ emailComponent: componentModule.default as EmailComponent,
113
+ render: componentModule.render as typeof render,
114
+ createElement:
115
+ componentModule.reactEmailCreateReactElement as typeof React.createElement,
149
116
 
150
117
  sourceMapToOriginalFile: sourceMapToEmail,
151
118
  };
@@ -0,0 +1,11 @@
1
+ import { getLineAndColumnFromOffset } from './get-line-and-column-from-offset';
2
+
3
+ test('getLineAndColumnFromOffset()', () => {
4
+ const content = `export default function MyEmail() {
5
+ return <div className="testing classes to make sure this is not removed" id="my-div" aria-label="my beautiful div">
6
+ inside the div, should also stay unchanged
7
+ </div>;
8
+ }`;
9
+ const offset = content.indexOf('className');
10
+ expect(getLineAndColumnFromOffset(offset, content)).toEqual([2, 15]);
11
+ });
@@ -0,0 +1,11 @@
1
+ export const getLineAndColumnFromOffset = (
2
+ offset: number,
3
+ content: string,
4
+ ): [line: number, column: number] => {
5
+ const lineBreaks = [...content.slice(0, offset).matchAll(/\n|\r|\r\n/g)];
6
+
7
+ const line = lineBreaks.length + 1;
8
+ const column = offset - (lineBreaks[lineBreaks.length - 1]?.index ?? 0);
9
+
10
+ return [line, column];
11
+ };
@@ -3,3 +3,4 @@ export * from './cn';
3
3
  export * from './copy-text-to-clipboard';
4
4
  export * from './language-map';
5
5
  export * from './unreachable';
6
+ export * from './sanitize';
@@ -0,0 +1,60 @@
1
+ import { checkImages } from '../actions/email-validation/check-images';
2
+ import { checkLinks } from '../actions/email-validation/check-links';
3
+ import type { LintingRow } from '../components/toolbar/linter';
4
+ import { loadStream } from './load-stream';
5
+
6
+ export interface LintingSource<T> {
7
+ getStream(): Promise<ReadableStream<T>>;
8
+ mapValue(value: NoInfer<T>): LintingRow | undefined;
9
+ }
10
+
11
+ function createSource<T>(source: LintingSource<T>): LintingSource<T> {
12
+ return source;
13
+ }
14
+
15
+ export function getLintingSources(
16
+ markup: string,
17
+
18
+ urlBase: string,
19
+ ): LintingSource<unknown>[] {
20
+ return [
21
+ createSource({
22
+ getStream() {
23
+ return checkImages(markup, urlBase);
24
+ },
25
+ mapValue(result) {
26
+ if (result && result.status !== 'success') {
27
+ return {
28
+ result: result,
29
+ source: 'image',
30
+ };
31
+ }
32
+ },
33
+ }),
34
+ createSource({
35
+ getStream() {
36
+ return checkLinks(markup);
37
+ },
38
+ mapValue(result) {
39
+ if (result && result.status !== 'success') {
40
+ return {
41
+ result: result,
42
+ source: 'link',
43
+ };
44
+ }
45
+ },
46
+ }),
47
+ ];
48
+ }
49
+
50
+ export async function* loadLintingRowsFrom(sources: LintingSource<unknown>[]) {
51
+ for await (const source of sources) {
52
+ const stream = await source.getStream();
53
+ for await (const value of loadStream(stream)) {
54
+ const row = source.mapValue(value);
55
+ if (row) {
56
+ yield row;
57
+ }
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,15 @@
1
+ export async function* loadStream<T>(stream: ReadableStream<T>) {
2
+ const reader = stream.getReader();
3
+ try {
4
+ while (true) {
5
+ const { value, done } = await reader.read();
6
+ if (done) {
7
+ break;
8
+ }
9
+
10
+ yield value;
11
+ }
12
+ } finally {
13
+ reader.releaseLock();
14
+ }
15
+ }
@@ -0,0 +1,49 @@
1
+ type Ok<T, _E> = {
2
+ value: T;
3
+ };
4
+ type Error<_T, E> = {
5
+ error: E;
6
+ };
7
+
8
+ /**
9
+ * Do not destructure this object, it is meant to have all fields together
10
+ * in the same object
11
+ */
12
+ export type Result<T, E> = Ok<T, E> | Error<T, E>;
13
+
14
+ export function isErr<T, E>(result: Result<T, E>): result is Error<T, E> {
15
+ return 'error' in result;
16
+ }
17
+
18
+ export function isOk<T, E>(result: Result<T, E>): result is Ok<T, E> {
19
+ return 'value' in result && !('error' in result);
20
+ }
21
+
22
+ export function mapResult<T, E, B>(
23
+ result: Result<T, E>,
24
+ callback: (value: T) => B,
25
+ ): Result<B, E> {
26
+ if (isOk(result)) {
27
+ return ok(callback(result.value));
28
+ }
29
+
30
+ return result;
31
+ }
32
+
33
+ export function ok<T, E>(value: NoInfer<T>): Ok<T, E>;
34
+ // biome-ignore lint/suspicious/noConfusingVoidType: This is required for void return types on functions that can still error
35
+ export function ok<T extends void = void, E = never>(value: void): Ok<void, E>;
36
+ export function ok<T, E>(value: NoInfer<T>): Ok<T, E> {
37
+ return {
38
+ value,
39
+ };
40
+ }
41
+
42
+ export function err<T, E>(error: NoInfer<E>): Error<T, E>;
43
+ // biome-ignore lint/suspicious/noConfusingVoidType: This is required for void return types on functions that can still error
44
+ export function err<T, E extends void = void>(error: void): Error<T, void>;
45
+ export function err<T, E>(error: NoInfer<E>): Error<T, E> {
46
+ return {
47
+ error,
48
+ };
49
+ }
@@ -0,0 +1,64 @@
1
+ import path from 'node:path';
2
+ import vm from 'node:vm';
3
+ import { type Result, err, ok } from './result';
4
+ import { staticNodeModulesForVM } from './static-node-modules-for-vm';
5
+
6
+ export const runBundledCode = (
7
+ code: string,
8
+ filename: string,
9
+ ): Result<unknown, unknown> => {
10
+ const fakeContext = {
11
+ ...global,
12
+ console,
13
+ Buffer,
14
+ AbortSignal,
15
+ Event,
16
+ EventTarget,
17
+ TextDecoder,
18
+ Request,
19
+ Response,
20
+ TextDecoderStream,
21
+ TextEncoder,
22
+ TextEncoderStream,
23
+ ReadableStream,
24
+ URL,
25
+ URLSearchParams,
26
+ Headers,
27
+ module: {
28
+ exports: {},
29
+ },
30
+ __filename: filename,
31
+ __dirname: path.dirname(filename),
32
+ require: (specifiedModule: string) => {
33
+ let m = specifiedModule;
34
+ if (specifiedModule.startsWith('node:')) {
35
+ m = m.split(':')[1]!;
36
+ }
37
+
38
+ if (m in staticNodeModulesForVM) {
39
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
40
+ return staticNodeModulesForVM[m];
41
+ }
42
+
43
+ // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-useless-template-literals
44
+ return require(`${specifiedModule}`) as unknown;
45
+ // this stupid string templating was necessary to not have
46
+ // webpack warnings like:
47
+ //
48
+ // Import trace for requested module:
49
+ // ./src/utils/get-email-component.tsx
50
+ // ./src/app/page.tsx
51
+ // ⚠ ./src/utils/get-email-component.tsx
52
+ // Critical dependency: the request of a dependency is an expression
53
+ },
54
+ process,
55
+ };
56
+
57
+ try {
58
+ vm.runInNewContext(code, fakeContext, { filename });
59
+ } catch (exception) {
60
+ return err(exception);
61
+ }
62
+
63
+ return ok(fakeContext.module.exports as unknown);
64
+ };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Sanitizes text by replacing underscores and hyphens with spaces
3
+ */
4
+ export const sanitize = (text: string): string => {
5
+ return text.replace(/[_-]/g, ' ');
6
+ };
@@ -0,0 +1,133 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+
3
+ declare module "tailwindcss/lib/lib/evaluateTailwindFunctions" {
4
+ import type { JitContext } from "tailwindcss/lib/lib/setupContextUtils";
5
+ import type { Root } from "postcss";
6
+
7
+ export default function evaluateTailwindFunctions(
8
+ context: JITContext,
9
+ ): (root: Root) => void;
10
+ }
11
+
12
+ declare module "tailwindcss/lib/lib/resolveDefaultsAtRules" {
13
+ import type { JitContext } from "tailwindcss/lib/lib/setupContextUtils";
14
+ import type { Root } from "postcss";
15
+
16
+ export default function expandApplyAtRules(
17
+ context: JITContext,
18
+ ): (root: Root) => void;
19
+ }
20
+
21
+ declare module "tailwindcss/lib/lib/partitionApplyAtRules" {
22
+ import type { Root } from "postcss";
23
+
24
+ export default function partitionApplyAtRules(): (root: Root) => void;
25
+ }
26
+
27
+ declare module "tailwindcss/lib/lib/substituteScreenAtRules" {
28
+ import type { Root } from "postcss";
29
+
30
+ export default function substituteScreenAtRules(
31
+ context: JITContext,
32
+ ): (root: Root) => void;
33
+ }
34
+
35
+ declare module "tailwindcss/lib/lib/resolveDefaultsAtRules" {
36
+ import type { JitContext } from "tailwindcss/lib/lib/setupContextUtils";
37
+ import type { Root } from "postcss";
38
+
39
+ export default function expandApplyAtRules(
40
+ context: JITContext,
41
+ ): (root: Root) => void;
42
+ }
43
+
44
+ declare module "tailwindcss/lib/lib/expandApplyAtRules" {
45
+ import type { JitContext } from "tailwindcss/lib/lib/setupContextUtils";
46
+ import type { Root } from "postcss";
47
+
48
+ export default function expandApplyAtRules(
49
+ context: JITContext,
50
+ ): (root: Root) => void;
51
+ }
52
+
53
+ declare module "tailwindcss/lib/lib/expandTailwindAtRules" {
54
+ import type { JitContext } from "tailwindcss/lib/lib/setupContextUtils";
55
+ import type { Root } from "postcss";
56
+
57
+ export default async function expandTailwindAtRules(
58
+ context: JITContext,
59
+ ): (root: Root) => Promise<void>;
60
+ }
61
+
62
+ declare module "tailwindcss/lib/lib/collapseAdjacentRules" {
63
+ import type { JitContext } from "tailwindcss/lib/lib/setupContextUtils";
64
+ import type { Root } from "postcss";
65
+
66
+ export default async function collapseAdjacentRules(
67
+ context: JITContext,
68
+ ): (root: Root) => void;
69
+ }
70
+
71
+ declare module "tailwindcss/lib/lib/collapseDuplicateDeclarations" {
72
+ import type { JitContext } from "tailwindcss/lib/lib/setupContextUtils";
73
+ import type { Root } from "postcss";
74
+
75
+ export default async function collapseDuplicateDeclarations(
76
+ context: JITContext,
77
+ ): (root: Root) => void;
78
+ }
79
+
80
+ declare module "tailwindcss/lib/lib/generateRules" {
81
+ import type { JitContext } from "tailwindcss/lib/lib/setupContextUtils";
82
+ import type { Rule } from "postcss";
83
+
84
+ export function generateRules(
85
+ classNames: Set<string>,
86
+ context: JITContext,
87
+ ): [bigint, Rule][];
88
+ }
89
+
90
+ // taken from https://github.com/vinicoder/tw-to-css/blob/main/types.d.ts
91
+ // thanks vinicoder!
92
+ declare module "tailwindcss/lib/lib/setupContextUtils" {
93
+ import type { Container, Node } from "postcss";
94
+ import type { Config } from "tailwindcss";
95
+ import type resolveConfig from "tailwindcss/resolveConfig";
96
+
97
+ interface ChangedContent {
98
+ content: string;
99
+ extension?: string;
100
+ }
101
+
102
+ interface Api {
103
+ container: Container;
104
+ separator: string;
105
+ format: (def: string) => void;
106
+ wrap: (rule: Container) => void;
107
+ }
108
+
109
+ type VariantPreview = string;
110
+
111
+ type VariantFn = [number, (api: Api) => VariantPreview | undefined];
112
+
113
+ type VariantName = string;
114
+
115
+ export interface JitContext {
116
+ changedContent: ChangedContent[];
117
+ ruleCache: Set<unknown>;
118
+ candidateRuleCache: Map<unknown, unknown>;
119
+ classCache: Map<unknown, unknown>;
120
+ applyClassCache: Map<unknown, unknown>;
121
+ notClassCache: Set<unknown>;
122
+ postCssNodeCache: Map<unknown, unknown>;
123
+
124
+ getClassList: () => string[];
125
+ tailwindConfig: TailwindConfig;
126
+ variantMap: Map<VariantName, VariantFn[]>;
127
+ }
128
+
129
+ export function createContext(
130
+ config: ReturnType<typeof resolveConfig>,
131
+ changedContent?: ChangedContent[],
132
+ ): JitContext;
133
+ }
package/tsconfig.json CHANGED
@@ -24,16 +24,22 @@
24
24
  "declarationMap": false,
25
25
  "incremental": false,
26
26
  "jsx": "preserve",
27
- "lib": ["dom", "dom.iterable", "esnext"],
27
+ "lib": ["dom", "dom.iterable", "esnext", "ESNext.AsyncIterable"],
28
28
  "noEmit": true,
29
29
  "strict": false,
30
- "target": "es5",
30
+ "target": "ESNext",
31
31
  "module": "CommonJS",
32
32
  "noUncheckedIndexedAccess": true,
33
33
  "resolveJsonModule": true,
34
34
  "types": ["vitest/globals"],
35
35
  "outDir": "dist"
36
36
  },
37
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
37
+ "include": [
38
+ "next-env.d.ts",
39
+ "tailwind-internals.d.ts",
40
+ "**/*.ts",
41
+ "**/*.tsx",
42
+ ".next/types/**/*.ts"
43
+ ],
38
44
  "exclude": [".next", "dist", "node_modules"]
39
45
  }
@@ -1,25 +0,0 @@
1
- import { spawn } from 'node:child_process';
2
- import fs from 'node:fs';
3
-
4
- const nextBuildProcess = spawn('pnpm', ['next', 'build'], {
5
- detached: true,
6
- shell: true,
7
- stdio: 'inherit',
8
- });
9
-
10
- process.on('SIGINT', () => {
11
- nextBuildProcess.kill('SIGINT');
12
- });
13
-
14
- nextBuildProcess.on('exit', (code) => {
15
- if (code !== 0) {
16
- console.error(`next build failed with exit code ${code}`);
17
- process.exit(code);
18
- }
19
-
20
- if (fs.existsSync('dist/preview')) {
21
- fs.rmSync('dist/preview', { recursive: true });
22
- }
23
- fs.mkdirSync('dist/preview', { recursive: true });
24
- fs.renameSync('.next', 'dist/preview/.next');
25
- });