@trypattern/sdk 0.0.0-stage → 0.1.1

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 (273) hide show
  1. package/README.md +127 -2
  2. package/dist/access.d.ts +31 -0
  3. package/dist/access.js +72 -0
  4. package/dist/actions.d.ts +57 -0
  5. package/dist/actions.js +198 -0
  6. package/dist/api.d.ts +30 -0
  7. package/dist/api.js +60 -0
  8. package/dist/app.d.ts +115 -0
  9. package/dist/app.js +266 -0
  10. package/dist/appInfo.d.ts +35 -0
  11. package/dist/appInfo.js +67 -0
  12. package/dist/appTitle.d.ts +6 -0
  13. package/dist/appTitle.js +3 -0
  14. package/dist/appearance.d.ts +7 -0
  15. package/dist/appearance.js +2 -0
  16. package/dist/auth/sessionApi.d.ts +41 -0
  17. package/dist/auth/sessionApi.js +158 -0
  18. package/dist/auth/sessionStore.d.ts +20 -0
  19. package/dist/auth/sessionStore.js +40 -0
  20. package/dist/auth/user.d.ts +11 -0
  21. package/dist/auth/user.js +9 -0
  22. package/dist/builtins/Account.d.ts +25 -0
  23. package/dist/builtins/Account.js +15 -0
  24. package/dist/builtins/AppSettings.d.ts +12 -0
  25. package/dist/builtins/AppSettings.js +31 -0
  26. package/dist/builtins/Palette.d.ts +29 -0
  27. package/dist/builtins/Palette.js +63 -0
  28. package/dist/builtins/SignIn.d.ts +28 -0
  29. package/dist/builtins/SignIn.js +60 -0
  30. package/dist/builtins/Toasts.d.ts +32 -0
  31. package/dist/builtins/Toasts.js +25 -0
  32. package/dist/builtins/actions.d.ts +2 -0
  33. package/dist/builtins/actions.js +45 -0
  34. package/dist/cache.d.ts +24 -0
  35. package/dist/cache.js +112 -0
  36. package/dist/cli/app.d.ts +2 -0
  37. package/dist/cli/app.js +1406 -0
  38. package/dist/cli/publishing.d.ts +21 -0
  39. package/dist/cli/publishing.js +493 -0
  40. package/dist/cli/screenshot.d.ts +1 -0
  41. package/dist/cli/screenshot.js +96 -0
  42. package/dist/cli/tools.d.ts +12 -0
  43. package/dist/cli/tools.js +35 -0
  44. package/dist/clock.d.ts +6 -0
  45. package/dist/clock.js +31 -0
  46. package/dist/components/AppFrame.d.ts +14 -0
  47. package/dist/components/AppFrame.js +64 -0
  48. package/dist/components/AppearancePicker.d.ts +1 -0
  49. package/dist/components/AppearancePicker.js +12 -0
  50. package/dist/components/Avatar.d.ts +9 -0
  51. package/dist/components/Avatar.js +63 -0
  52. package/dist/components/Badge.d.ts +11 -0
  53. package/dist/components/Badge.js +87 -0
  54. package/dist/components/Button.d.ts +24 -0
  55. package/dist/components/Button.js +252 -0
  56. package/dist/components/Card.d.ts +12 -0
  57. package/dist/components/Card.js +38 -0
  58. package/dist/components/CommandMenu.d.ts +23 -0
  59. package/dist/components/CommandMenu.js +214 -0
  60. package/dist/components/CommandPalette.d.ts +1 -0
  61. package/dist/components/CommandPalette.js +7 -0
  62. package/dist/components/Content.d.ts +7 -0
  63. package/dist/components/Content.js +24 -0
  64. package/dist/components/ContextMenu.d.ts +8 -0
  65. package/dist/components/ContextMenu.js +23 -0
  66. package/dist/components/CopyButton.d.ts +11 -0
  67. package/dist/components/CopyButton.js +16 -0
  68. package/dist/components/Dialog.d.ts +11 -0
  69. package/dist/components/Dialog.js +81 -0
  70. package/dist/components/Divider.d.ts +7 -0
  71. package/dist/components/Divider.js +30 -0
  72. package/dist/components/EmptyState.d.ts +8 -0
  73. package/dist/components/EmptyState.js +24 -0
  74. package/dist/components/Form.d.ts +9 -0
  75. package/dist/components/Form.js +23 -0
  76. package/dist/components/Icon.d.ts +10 -0
  77. package/dist/components/Icon.js +22 -0
  78. package/dist/components/IconButton.d.ts +14 -0
  79. package/dist/components/IconButton.js +53 -0
  80. package/dist/components/Kbd.d.ts +6 -0
  81. package/dist/components/Kbd.js +67 -0
  82. package/dist/components/Link.d.ts +14 -0
  83. package/dist/components/Link.js +44 -0
  84. package/dist/components/ListRow.d.ts +12 -0
  85. package/dist/components/ListRow.js +62 -0
  86. package/dist/components/LoadingRows.d.ts +5 -0
  87. package/dist/components/LoadingRows.js +6 -0
  88. package/dist/components/Menu.d.ts +12 -0
  89. package/dist/components/Menu.js +33 -0
  90. package/dist/components/MenuItems.d.ts +21 -0
  91. package/dist/components/MenuItems.js +32 -0
  92. package/dist/components/Placement.d.ts +21 -0
  93. package/dist/components/Placement.js +1 -0
  94. package/dist/components/Popup.d.ts +8 -0
  95. package/dist/components/Popup.js +43 -0
  96. package/dist/components/PopupItem.d.ts +8 -0
  97. package/dist/components/PopupItem.js +51 -0
  98. package/dist/components/PropertyRow.d.ts +7 -0
  99. package/dist/components/PropertyRow.js +23 -0
  100. package/dist/components/RadioGroup.d.ts +18 -0
  101. package/dist/components/RadioGroup.js +85 -0
  102. package/dist/components/Row.d.ts +16 -0
  103. package/dist/components/Row.js +27 -0
  104. package/dist/components/Screen.d.ts +13 -0
  105. package/dist/components/Screen.js +56 -0
  106. package/dist/components/Scrim.d.ts +8 -0
  107. package/dist/components/Scrim.js +40 -0
  108. package/dist/components/ScrollArea.d.ts +15 -0
  109. package/dist/components/ScrollArea.js +13 -0
  110. package/dist/components/SegmentedControl.d.ts +17 -0
  111. package/dist/components/SegmentedControl.js +68 -0
  112. package/dist/components/Select.d.ts +17 -0
  113. package/dist/components/Select.js +107 -0
  114. package/dist/components/SettingsRow.d.ts +8 -0
  115. package/dist/components/SettingsRow.js +32 -0
  116. package/dist/components/Sidebar.d.ts +20 -0
  117. package/dist/components/Sidebar.js +137 -0
  118. package/dist/components/SignInScreen.d.ts +1 -0
  119. package/dist/components/SignInScreen.js +40 -0
  120. package/dist/components/Size.d.ts +1 -0
  121. package/dist/components/Size.js +1 -0
  122. package/dist/components/Skeleton.d.ts +8 -0
  123. package/dist/components/Skeleton.js +48 -0
  124. package/dist/components/Spinner.d.ts +6 -0
  125. package/dist/components/Spinner.js +54 -0
  126. package/dist/components/Stack.d.ts +14 -0
  127. package/dist/components/Stack.js +19 -0
  128. package/dist/components/Switch.d.ts +10 -0
  129. package/dist/components/Switch.js +72 -0
  130. package/dist/components/Text.d.ts +21 -0
  131. package/dist/components/Text.js +77 -0
  132. package/dist/components/TextField.d.ts +25 -0
  133. package/dist/components/TextField.js +68 -0
  134. package/dist/components/Toaster.d.ts +1 -0
  135. package/dist/components/Toaster.js +83 -0
  136. package/dist/components/Tooltip.d.ts +8 -0
  137. package/dist/components/Tooltip.js +43 -0
  138. package/dist/components/controlLook.d.ts +70 -0
  139. package/dist/components/controlLook.js +80 -0
  140. package/dist/components/frame.d.ts +4 -0
  141. package/dist/components/frame.js +2 -0
  142. package/dist/components/icons.d.ts +59 -0
  143. package/dist/components/icons.js +111 -0
  144. package/dist/components/interaction.d.ts +12 -0
  145. package/dist/components/interaction.js +22 -0
  146. package/dist/components/spacing.d.ts +118 -0
  147. package/dist/components/spacing.js +46 -0
  148. package/dist/components/tones.d.ts +28 -0
  149. package/dist/components/tones.js +12 -0
  150. package/dist/entityStore.d.ts +46 -0
  151. package/dist/entityStore.js +160 -0
  152. package/dist/flows/accessFlows.d.ts +9 -0
  153. package/dist/flows/accessFlows.js +167 -0
  154. package/dist/flows/browser.d.ts +34 -0
  155. package/dist/flows/browser.js +167 -0
  156. package/dist/flows/contactSheet.d.ts +1 -0
  157. package/dist/flows/contactSheet.js +63 -0
  158. package/dist/flows/flowBrowser.d.ts +25 -0
  159. package/dist/flows/flowBrowser.js +23 -0
  160. package/dist/flows/flowSetup.d.ts +3 -0
  161. package/dist/flows/flowSetup.js +35 -0
  162. package/dist/flows/flows.d.ts +47 -0
  163. package/dist/flows/flows.js +175 -0
  164. package/dist/flows/screenshotName.d.ts +1 -0
  165. package/dist/flows/screenshotName.js +6 -0
  166. package/dist/gate.d.ts +8 -0
  167. package/dist/gate.js +52 -0
  168. package/dist/hooks/appContext.d.ts +15 -0
  169. package/dist/hooks/appContext.js +5 -0
  170. package/dist/hooks/appHooks.d.ts +21 -0
  171. package/dist/hooks/appHooks.js +24 -0
  172. package/dist/hooks/useAnimatesOnChange.d.ts +1 -0
  173. package/dist/hooks/useAnimatesOnChange.js +14 -0
  174. package/dist/hooks/useStoreState.d.ts +6 -0
  175. package/dist/hooks/useStoreState.js +5 -0
  176. package/dist/index.d.ts +44 -0
  177. package/dist/index.js +21 -0
  178. package/dist/memoryApi.d.ts +19 -0
  179. package/dist/memoryApi.js +42 -0
  180. package/dist/model.d.ts +94 -0
  181. package/dist/model.js +243 -0
  182. package/dist/page.d.ts +11 -0
  183. package/dist/page.js +9 -0
  184. package/dist/platform.d.ts +1 -0
  185. package/dist/platform.js +5 -0
  186. package/dist/publishWire.d.ts +271 -0
  187. package/dist/publishWire.js +168 -0
  188. package/dist/react.d.ts +49 -0
  189. package/dist/react.js +40 -0
  190. package/dist/registered.d.ts +60 -0
  191. package/dist/registered.js +19 -0
  192. package/dist/routes.d.ts +38 -0
  193. package/dist/routes.js +21 -0
  194. package/dist/rules/checkAllFiles.d.ts +1 -0
  195. package/dist/rules/checkAllFiles.js +749 -0
  196. package/dist/rules/checkApiRouting.d.ts +1 -0
  197. package/dist/rules/checkApiRouting.js +112 -0
  198. package/dist/rules/lintPlugin.d.ts +252 -0
  199. package/dist/rules/lintPlugin.js +1159 -0
  200. package/dist/rules/messages.d.ts +48 -0
  201. package/dist/rules/messages.js +48 -0
  202. package/dist/rules/themeTokens.d.ts +6 -0
  203. package/dist/rules/themeTokens.js +64 -0
  204. package/dist/schema.d.ts +4 -0
  205. package/dist/schema.js +2 -0
  206. package/dist/server/auth.d.ts +115 -0
  207. package/dist/server/auth.js +134 -0
  208. package/dist/server/authService.d.ts +9 -0
  209. package/dist/server/authService.js +50 -0
  210. package/dist/server/createServer.d.ts +402 -0
  211. package/dist/server/createServer.js +82 -0
  212. package/dist/server/env.d.ts +15 -0
  213. package/dist/server/env.js +1 -0
  214. package/dist/server/index.d.ts +13 -0
  215. package/dist/server/index.js +9 -0
  216. package/dist/server/services.d.ts +9 -0
  217. package/dist/server/services.js +47 -0
  218. package/dist/server/session.d.ts +29 -0
  219. package/dist/server/session.js +65 -0
  220. package/dist/server/sessions.d.ts +92 -0
  221. package/dist/server/sessions.js +47 -0
  222. package/dist/server/settings.d.ts +49 -0
  223. package/dist/server/settings.js +25 -0
  224. package/dist/server/signInWire.d.ts +53 -0
  225. package/dist/server/signInWire.js +23 -0
  226. package/dist/server/tables.d.ts +242 -0
  227. package/dist/server/tables.js +246 -0
  228. package/dist/server/users.d.ts +189 -0
  229. package/dist/server/users.js +49 -0
  230. package/dist/serviceClients.d.ts +5 -0
  231. package/dist/serviceClients.js +70 -0
  232. package/dist/services.d.ts +53 -0
  233. package/dist/services.js +30 -0
  234. package/dist/shortcuts.d.ts +3 -0
  235. package/dist/shortcuts.js +40 -0
  236. package/dist/start.d.ts +20 -0
  237. package/dist/start.js +145 -0
  238. package/dist/state.d.ts +14 -0
  239. package/dist/state.js +17 -0
  240. package/dist/store.d.ts +45 -0
  241. package/dist/store.js +208 -0
  242. package/dist/subscribers.d.ts +5 -0
  243. package/dist/subscribers.js +13 -0
  244. package/dist/table.d.ts +143 -0
  245. package/dist/table.js +91 -0
  246. package/dist/tables.d.ts +77 -0
  247. package/dist/tables.js +216 -0
  248. package/dist/theme/reset.css +84 -0
  249. package/dist/theme/scales.d.ts +76 -0
  250. package/dist/theme/scales.js +82 -0
  251. package/dist/theme/tokens.stylex.d.ts +218 -0
  252. package/dist/theme/tokens.stylex.js +233 -0
  253. package/dist/vite/commandBridge.d.ts +2 -0
  254. package/dist/vite/commandBridge.js +46 -0
  255. package/dist/vite/designManifest.d.ts +25 -0
  256. package/dist/vite/designManifest.js +191 -0
  257. package/dist/vite/localWorker.d.ts +4 -0
  258. package/dist/vite/localWorker.js +60 -0
  259. package/dist/vite/projectFile.d.ts +9 -0
  260. package/dist/vite/projectFile.js +17 -0
  261. package/dist/vite/rootAlias.d.ts +2 -0
  262. package/dist/vite/rootAlias.js +15 -0
  263. package/dist/vite/vite.d.ts +8 -0
  264. package/dist/vite/vite.js +95 -0
  265. package/dist/wire.d.ts +7 -0
  266. package/dist/wire.js +1 -0
  267. package/package.json +103 -3
  268. package/presets/lintPlugin.js +7 -0
  269. package/presets/oxlint.json +167 -0
  270. package/presets/register.d.ts +21 -0
  271. package/presets/server.d.ts +2 -0
  272. package/presets/tsconfig.app.json +29 -0
  273. package/presets/tsconfig.server.json +27 -0
@@ -0,0 +1,749 @@
1
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
2
+ import { basename, dirname, extname, join, relative } from 'node:path';
3
+ import ts from 'typescript';
4
+ import { themeFileOf, themeTokens } from "./themeTokens.js";
5
+ import { ascendingScales, pixelGroups, scaleGroups, textInControls, textLines } from "../theme/scales.js";
6
+ import { IMPORT_FROM_ROOT_MESSAGE, NO_COMMENTS_MESSAGE, NO_RE_EXPORTS_MESSAGE, NO_UNUSED_EXPORTS_MESSAGE, NO_POINTER_CURSOR_MESSAGE, ACCESS_FLOWS_MESSAGE, TOASTER_ONCE_MESSAGE, SCHEMA_TABLES_MESSAGE, SCRIPTS_WIRED_MESSAGE, SRC_STRUCTURE_MESSAGE, SCROLL_VIEW_ONLY_MESSAGE, THEME_SCALES_MESSAGE } from "./messages.js";
7
+ const ROOT = process.cwd();
8
+ const SKIPPED_DIRECTORIES = new Set(['node_modules', 'dist', '.dev', '.git']);
9
+ const SKIPPED_FILES = new Set(['pnpm-lock.yaml']);
10
+ const SKIPPED_EXTENSIONS = new Set(['.md']);
11
+ const HASH_LINE_COMMENT_FILES = new Set(['.gitignore', '.nvmrc']);
12
+ const SHELL_SHEBANG = /^#!\s*\/(usr\/bin\/env\s+)?(bin\/)?(sh|bash|zsh)\b/;
13
+ const SHELL_WORD_BOUNDARIES = new Set([
14
+ '\n',
15
+ ' ',
16
+ '\t',
17
+ ';',
18
+ '&',
19
+ '|',
20
+ '(',
21
+ ')'
22
+ ]);
23
+ const MESSAGES = {
24
+ 'import-from-root': IMPORT_FROM_ROOT_MESSAGE,
25
+ 'no-comments': NO_COMMENTS_MESSAGE,
26
+ 'no-re-exports': NO_RE_EXPORTS_MESSAGE,
27
+ 'no-unused-exports': NO_UNUSED_EXPORTS_MESSAGE,
28
+ 'no-pointer-cursor': NO_POINTER_CURSOR_MESSAGE,
29
+ 'scroll-view-only': SCROLL_VIEW_ONLY_MESSAGE,
30
+ 'schema-tables': SCHEMA_TABLES_MESSAGE,
31
+ 'access-flows': ACCESS_FLOWS_MESSAGE,
32
+ 'toaster-once': TOASTER_ONCE_MESSAGE,
33
+ 'scripts-wired': SCRIPTS_WIRED_MESSAGE,
34
+ 'src-structure': SRC_STRUCTURE_MESSAGE,
35
+ 'theme-scales': THEME_SCALES_MESSAGE
36
+ };
37
+ const BANNED_CSS_DECLARATIONS = [
38
+ { pattern: /^\s*cursor\s*:[^]*\bpointer\b/i, rule: 'no-pointer-cursor' },
39
+ {
40
+ pattern: /^\s*overflow(-x|-y|-block|-inline)?\s*:[^]*\b(auto|scroll)\b/i,
41
+ rule: 'scroll-view-only'
42
+ }
43
+ ];
44
+ function comment(offset) {
45
+ return { offset, rule: 'no-comments' };
46
+ }
47
+ const SAME_FOLDER_IMPORT = /^\.\/[^/]+$/;
48
+ function importSpecifiers(source) {
49
+ const found = [];
50
+ const visit = (node) => {
51
+ if ((ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) &&
52
+ node.moduleSpecifier !== undefined &&
53
+ ts.isStringLiteralLike(node.moduleSpecifier)) {
54
+ found.push(node.moduleSpecifier);
55
+ }
56
+ if (ts.isCallExpression(node) &&
57
+ node.expression.kind === ts.SyntaxKind.ImportKeyword &&
58
+ node.arguments[0] !== undefined &&
59
+ ts.isStringLiteralLike(node.arguments[0])) {
60
+ found.push(node.arguments[0]);
61
+ }
62
+ if (ts.isImportTypeNode(node) &&
63
+ ts.isLiteralTypeNode(node.argument) &&
64
+ ts.isStringLiteralLike(node.argument.literal)) {
65
+ found.push(node.argument.literal);
66
+ }
67
+ ts.forEachChild(node, visit);
68
+ };
69
+ visit(source);
70
+ return found;
71
+ }
72
+ function relativeImports(source) {
73
+ return importSpecifiers(source)
74
+ .filter((specifier) => specifier.text.startsWith('.') &&
75
+ !SAME_FOLDER_IMPORT.test(specifier.text))
76
+ .map((specifier) => ({
77
+ offset: specifier.getStart(source),
78
+ rule: 'import-from-root'
79
+ }));
80
+ }
81
+ function scanScript(kind) {
82
+ return (text) => {
83
+ const source = ts.createSourceFile('file', text, ts.ScriptTarget.Latest, true, kind);
84
+ const starts = new Set();
85
+ const visit = (node) => {
86
+ if (node.kind >= ts.SyntaxKind.FirstJSDocNode &&
87
+ node.kind <= ts.SyntaxKind.LastJSDocNode) {
88
+ return;
89
+ }
90
+ if (node.kind !== ts.SyntaxKind.JsxText) {
91
+ for (const range of ts.getLeadingCommentRanges(text, node.getFullStart()) ?? []) {
92
+ starts.add(range.pos);
93
+ }
94
+ }
95
+ for (const child of node.getChildren(source))
96
+ visit(child);
97
+ };
98
+ visit(source);
99
+ return [
100
+ ...[...starts].sort((a, b) => a - b).map(comment),
101
+ ...relativeImports(source)
102
+ ];
103
+ };
104
+ }
105
+ function skipQuoted(text, start, escapes) {
106
+ const quote = text[start];
107
+ let i = start + 1;
108
+ while (i < text.length && text[i] !== quote) {
109
+ i += escapes && text[i] === '\\' ? 2 : 1;
110
+ }
111
+ return i + 1;
112
+ }
113
+ function scanCss(text) {
114
+ const findings = [];
115
+ let declarationText = '';
116
+ let declarationStart = 0;
117
+ let i = 0;
118
+ while (i < text.length) {
119
+ const char = text[i];
120
+ if (char === '"' || char === "'") {
121
+ const end = skipQuoted(text, i, true);
122
+ declarationText += ' '.repeat(end - i);
123
+ i = end;
124
+ }
125
+ else if (char === '/' && text[i + 1] === '*') {
126
+ findings.push(comment(i));
127
+ const close = text.indexOf('*/', i + 2);
128
+ i = close === -1 ? text.length : close + 2;
129
+ }
130
+ else if (/^url\(\s*[^"'\s]/i.test(text.slice(i, i + 6))) {
131
+ const close = text.indexOf(')', i);
132
+ i = close === -1 ? text.length : close + 1;
133
+ }
134
+ else if (char === ';' || char === '{' || char === '}') {
135
+ for (const banned of BANNED_CSS_DECLARATIONS) {
136
+ if (banned.pattern.test(declarationText)) {
137
+ findings.push({ offset: declarationStart, rule: banned.rule });
138
+ }
139
+ }
140
+ declarationText = '';
141
+ declarationStart = i + 1;
142
+ i += 1;
143
+ }
144
+ else {
145
+ if (declarationText.trim() === '' && !/\s/.test(char ?? '')) {
146
+ declarationStart = i;
147
+ }
148
+ declarationText += char;
149
+ i += 1;
150
+ }
151
+ }
152
+ return findings;
153
+ }
154
+ function scanJson(text) {
155
+ const findings = [];
156
+ let i = 0;
157
+ while (i < text.length) {
158
+ const char = text[i];
159
+ if (char === '"') {
160
+ i = skipQuoted(text, i, true);
161
+ }
162
+ else if (char === '/' && (text[i + 1] === '/' || text[i + 1] === '*')) {
163
+ findings.push(comment(i));
164
+ const close = text[i + 1] === '/' ? text.indexOf('\n', i) : text.indexOf('*/', i + 2);
165
+ i = close === -1 ? text.length : close + 2;
166
+ }
167
+ else {
168
+ i += 1;
169
+ }
170
+ }
171
+ return findings;
172
+ }
173
+ function scanEmbedded(scanner, text, offset) {
174
+ return scanner(text).map((finding) => ({
175
+ ...finding,
176
+ offset: finding.offset + offset
177
+ }));
178
+ }
179
+ function scanMarkup(text) {
180
+ const findings = [];
181
+ let i = 0;
182
+ while (i < text.length) {
183
+ if (text.startsWith('<!--', i)) {
184
+ findings.push(comment(i));
185
+ const close = text.indexOf('-->', i + 4);
186
+ i = close === -1 ? text.length : close + 3;
187
+ continue;
188
+ }
189
+ if (text[i] !== '<') {
190
+ i += 1;
191
+ continue;
192
+ }
193
+ const tagName = /^<([a-zA-Z][\w-]*)/.exec(text.slice(i))?.[1]?.toLowerCase();
194
+ let j = i + 1;
195
+ while (j < text.length && text[j] !== '>') {
196
+ j =
197
+ text[j] === '"' || text[j] === "'" ? skipQuoted(text, j, false) : j + 1;
198
+ }
199
+ i = j + 1;
200
+ if (tagName === 'script' || tagName === 'style') {
201
+ const closeTag = text.toLowerCase().indexOf(`</${tagName}`, i);
202
+ const end = closeTag === -1 ? text.length : closeTag;
203
+ const scanner = tagName === 'style' ? scanCss : scanScript(ts.ScriptKind.JS);
204
+ findings.push(...scanEmbedded(scanner, text.slice(i, end), i));
205
+ i = end;
206
+ }
207
+ }
208
+ return findings;
209
+ }
210
+ function scanShell(text) {
211
+ const findings = [];
212
+ let i = text.startsWith('#!') ? text.indexOf('\n') + 1 || text.length : 0;
213
+ while (i < text.length) {
214
+ const char = text[i];
215
+ if (char === "'") {
216
+ i = skipQuoted(text, i, false);
217
+ }
218
+ else if (char === '"') {
219
+ i = skipQuoted(text, i, true);
220
+ }
221
+ else if (char === '\\') {
222
+ i += 2;
223
+ }
224
+ else if (char === '#' &&
225
+ (i === 0 || SHELL_WORD_BOUNDARIES.has(text[i - 1] ?? ''))) {
226
+ findings.push(comment(i));
227
+ const close = text.indexOf('\n', i);
228
+ i = close === -1 ? text.length : close;
229
+ }
230
+ else {
231
+ i += 1;
232
+ }
233
+ }
234
+ return findings;
235
+ }
236
+ function scanYaml(text) {
237
+ const findings = [];
238
+ let i = 0;
239
+ while (i < text.length) {
240
+ const char = text[i];
241
+ if (char === "'" || char === '"') {
242
+ i = skipQuoted(text, i, char === '"');
243
+ }
244
+ else if (char === '#' &&
245
+ (i === 0 || [' ', '\t', '\n'].includes(text[i - 1] ?? ''))) {
246
+ findings.push(comment(i));
247
+ const close = text.indexOf('\n', i);
248
+ i = close === -1 ? text.length : close;
249
+ }
250
+ else {
251
+ i += 1;
252
+ }
253
+ }
254
+ return findings;
255
+ }
256
+ function scanHashLines(text) {
257
+ const findings = [];
258
+ let lineStart = 0;
259
+ for (const line of text.split('\n')) {
260
+ if (line.trimStart().startsWith('#')) {
261
+ findings.push(comment(lineStart + line.indexOf('#')));
262
+ }
263
+ lineStart += line.length + 1;
264
+ }
265
+ return findings;
266
+ }
267
+ const SCANNERS_BY_EXTENSION = {
268
+ '.ts': scanScript(ts.ScriptKind.TS),
269
+ '.mts': scanScript(ts.ScriptKind.TS),
270
+ '.cts': scanScript(ts.ScriptKind.TS),
271
+ '.tsx': scanScript(ts.ScriptKind.TSX),
272
+ '.js': scanScript(ts.ScriptKind.JS),
273
+ '.mjs': scanScript(ts.ScriptKind.JS),
274
+ '.cjs': scanScript(ts.ScriptKind.JS),
275
+ '.jsx': scanScript(ts.ScriptKind.JSX),
276
+ '.css': scanCss,
277
+ '.json': scanJson,
278
+ '.html': scanMarkup,
279
+ '.svg': scanMarkup,
280
+ '.yaml': scanYaml,
281
+ '.yml': scanYaml
282
+ };
283
+ function scannerFor(path, text) {
284
+ const name = basename(path);
285
+ if (HASH_LINE_COMMENT_FILES.has(name))
286
+ return scanHashLines;
287
+ const extension = extname(name);
288
+ if (extension !== '')
289
+ return SCANNERS_BY_EXTENSION[extension];
290
+ return SHELL_SHEBANG.test(text) ? scanShell : undefined;
291
+ }
292
+ function ownedFiles(directory) {
293
+ return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
294
+ const path = join(directory, entry.name);
295
+ if (entry.isDirectory()) {
296
+ return SKIPPED_DIRECTORIES.has(entry.name) ? [] : ownedFiles(path);
297
+ }
298
+ if (SKIPPED_FILES.has(entry.name) ||
299
+ SKIPPED_EXTENSIONS.has(extname(entry.name))) {
300
+ return [];
301
+ }
302
+ return [path];
303
+ });
304
+ }
305
+ function lineAndColumn(text, offset) {
306
+ const before = text.slice(0, offset).split('\n');
307
+ return `${before.length}:${(before.at(-1)?.length ?? 0) + 1}`;
308
+ }
309
+ const SCRIPTS_DIRECTORY = join(ROOT, 'scripts');
310
+ const FLOWS_DIRECTORY = join(ROOT, 'flows');
311
+ const SCRIPT_WIRING_FILES = ['package.json', 'app', 'vite.config.ts'];
312
+ function unwiredScripts() {
313
+ if (!existsSync(SCRIPTS_DIRECTORY))
314
+ return [];
315
+ const scripts = ownedFiles(SCRIPTS_DIRECTORY).filter((path) => extname(path) === '.ts');
316
+ const wiring = SCRIPT_WIRING_FILES.map((file) => readFileSync(join(ROOT, file), 'utf8')).join('\n');
317
+ const sources = [...scripts, ...ownedFiles(FLOWS_DIRECTORY)].map((path) => ({
318
+ path,
319
+ text: readFileSync(path, 'utf8')
320
+ }));
321
+ return scripts.filter((path) => {
322
+ const isInLabelledFolder = relative(SCRIPTS_DIRECTORY, path).includes('/');
323
+ const name = basename(path);
324
+ const isUsed = wiring.includes(relative(ROOT, path)) ||
325
+ sources.some((other) => other.path !== path &&
326
+ (other.text.includes(`/${name}'`) || other.text.includes(`'${name}'`)));
327
+ return !isInLabelledFolder || !isUsed;
328
+ });
329
+ }
330
+ const SRC_DIRECTORY = join(ROOT, 'src');
331
+ const SRC_ENTRY_FILES = new Set([
332
+ 'App.ts',
333
+ 'models/index.ts',
334
+ 'stores/index.ts',
335
+ 'services/index.ts',
336
+ 'actions/index.ts',
337
+ 'main.tsx',
338
+ 'routes.tsx',
339
+ 'index.css'
340
+ ]);
341
+ const SRC_FOLDER_PATTERNS = [
342
+ /^theme\/(theme|icons)\.ts$/,
343
+ /^lib\/[a-z][A-Za-z]*\.ts$/,
344
+ /^hooks\/use[A-Z][A-Za-z]*\.ts$/,
345
+ /^screens\/[A-Z][A-Za-z]*Screen\.tsx$/,
346
+ /^layouts\/[A-Z][A-Za-z]*Layout\.tsx$/,
347
+ /^models\/[A-Z][A-Za-z]*\.ts$/,
348
+ /^stores\/[A-Z][A-Za-z]*\.ts$/,
349
+ /^services\/[a-z][A-Za-z]*\.ts$/,
350
+ /^actions\/[a-z][A-Za-z]*\.ts$/
351
+ ];
352
+ const PREFIXED_FOLDER_FILE = /^(components)\/([A-Z][A-Za-z]*)\/([A-Z][A-Za-z]*)\.tsx?$/;
353
+ function isWhereItBelongs(path) {
354
+ if (SRC_ENTRY_FILES.has(path))
355
+ return true;
356
+ if (SRC_FOLDER_PATTERNS.some((pattern) => pattern.test(path)))
357
+ return true;
358
+ const grouped = PREFIXED_FOLDER_FILE.exec(path);
359
+ const folder = grouped?.[2];
360
+ const file = grouped?.[3];
361
+ return folder !== undefined && file !== undefined && file.startsWith(folder);
362
+ }
363
+ const MODULE_FILE = /\.(ts|tsx|mts|cts)$/;
364
+ const CLASS_HOMES = [
365
+ { pattern: /\bextends\s+Model\s*\(/, folder: 'models/' },
366
+ { pattern: /\bextends\s+Store\s*\(/, folder: 'stores/' },
367
+ { pattern: /\bdefineServices\s*\(/, folder: 'services/' },
368
+ { pattern: /\bdefineActions\s*\(/, folder: 'actions/' }
369
+ ];
370
+ function isInItsFolder(path) {
371
+ const inSrc = relative(SRC_DIRECTORY, path);
372
+ if (!MODULE_FILE.test(path))
373
+ return true;
374
+ const text = readFileSync(path, 'utf8');
375
+ return CLASS_HOMES.every(({ pattern, folder }) => !pattern.test(text) || inSrc.startsWith(folder));
376
+ }
377
+ function misplacedSourceFiles() {
378
+ return ownedFiles(SRC_DIRECTORY).filter((path) => !isWhereItBelongs(relative(SRC_DIRECTORY, path)) || !isInItsFolder(path));
379
+ }
380
+ const SCHEMA_FILE = join(ROOT, 'schema.ts');
381
+ function rowTypeName(table) {
382
+ const one = table.endsWith('ies')
383
+ ? `${table.slice(0, -3)}y`
384
+ : table.endsWith('s')
385
+ ? table.slice(0, -1)
386
+ : table;
387
+ return `${one.charAt(0).toUpperCase()}${one.slice(1)}`;
388
+ }
389
+ function isExported(node) {
390
+ return (ts.canHaveModifiers(node) &&
391
+ (ts.getModifiers(node) ?? []).some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword));
392
+ }
393
+ function schemaFindings() {
394
+ if (!existsSync(SCHEMA_FILE))
395
+ return [];
396
+ const text = readFileSync(SCHEMA_FILE, 'utf8');
397
+ const source = ts.createSourceFile(SCHEMA_FILE, text, ts.ScriptTarget.Latest, true);
398
+ const tables = [];
399
+ const rowTypes = new Set();
400
+ let exportsTables = false;
401
+ let exportsAdmins = false;
402
+ const visit = (node) => {
403
+ if (ts.isVariableStatement(node) &&
404
+ isExported(node) &&
405
+ node.declarationList.declarations.some((declaration) => ts.isIdentifier(declaration.name) &&
406
+ declaration.name.text === 'admins')) {
407
+ exportsAdmins = true;
408
+ }
409
+ if (ts.isVariableStatement(node) &&
410
+ isExported(node) &&
411
+ node.declarationList.declarations.some((declaration) => ts.isIdentifier(declaration.name) &&
412
+ declaration.name.text === 'tables' &&
413
+ declaration.initializer !== undefined &&
414
+ ts.isCallExpression(declaration.initializer) &&
415
+ declaration.initializer.expression.getText(source) === 'defineTables')) {
416
+ exportsTables = true;
417
+ }
418
+ if (ts.isCallExpression(node) &&
419
+ node.expression.getText(source) === 'defineTables') {
420
+ const [definitions] = node.arguments;
421
+ if (definitions !== undefined &&
422
+ ts.isObjectLiteralExpression(definitions)) {
423
+ for (const property of definitions.properties) {
424
+ const name = property.name;
425
+ if (name !== undefined &&
426
+ (ts.isIdentifier(name) || ts.isStringLiteral(name))) {
427
+ tables.push({ key: name.text, offset: property.getStart(source) });
428
+ }
429
+ }
430
+ }
431
+ }
432
+ if (ts.isTypeAliasDeclaration(node) &&
433
+ isExported(node) &&
434
+ ts.isTypeReferenceNode(node.type) &&
435
+ node.type.typeName.getText(source) === 'Row') {
436
+ const [argument] = node.type.typeArguments ?? [];
437
+ if (argument !== undefined && ts.isTypeQueryNode(argument)) {
438
+ rowTypes.add(argument.exprName.getText(source));
439
+ }
440
+ }
441
+ ts.forEachChild(node, visit);
442
+ };
443
+ visit(source);
444
+ if (!exportsTables) {
445
+ return [
446
+ {
447
+ offset: 0,
448
+ fix: 'export const tables = defineTables({ ... })'
449
+ }
450
+ ];
451
+ }
452
+ const missingAdmins = exportsAdmins
453
+ ? []
454
+ : [{ offset: 0, fix: 'export const admins: string[] = []' }];
455
+ return [
456
+ ...missingAdmins,
457
+ ...tables
458
+ .filter(({ key }) => !rowTypes.has(`tables.${key}`))
459
+ .map(({ key, offset }) => ({
460
+ offset,
461
+ fix: `export type ${rowTypeName(key)} = Row<typeof tables.${key}>`
462
+ }))
463
+ ];
464
+ }
465
+ const ACCESS_FLOWS_FILE = join(ROOT, 'flows', 'Access.flow.ts');
466
+ function toasterPlacements() {
467
+ return ownedFiles(SRC_DIRECTORY)
468
+ .filter((path) => path.endsWith('.tsx'))
469
+ .reduce((count, path) => count + (readFileSync(path, 'utf8').match(/<Toaster\b/g)?.length ?? 0), 0);
470
+ }
471
+ function runsAccessFlows() {
472
+ if (!existsSync(SCHEMA_FILE))
473
+ return true;
474
+ if (!existsSync(ACCESS_FLOWS_FILE))
475
+ return false;
476
+ const text = readFileSync(ACCESS_FLOWS_FILE, 'utf8');
477
+ return (/from '@trypattern\/sdk\/flows'/.test(text) &&
478
+ /accessFlows\(\s*\{\s*App\s*\}\s*\)/.test(text));
479
+ }
480
+ const WORKER_ENTRY = 'server/worker.ts';
481
+ const CONVENTION_EXPORTS = {
482
+ 'schema.ts': ['tables', 'admins'],
483
+ 'seed.ts': ['seed'],
484
+ 'vite.config.ts': ['default'],
485
+ 'server/worker.ts': ['default']
486
+ };
487
+ const EVERYTHING = '*';
488
+ function bindingNames(name) {
489
+ if (ts.isIdentifier(name))
490
+ return [name];
491
+ return name.elements.flatMap((element) => ts.isOmittedExpression(element) ? [] : bindingNames(element.name));
492
+ }
493
+ function resolveSpecifier(from, specifier) {
494
+ if (specifier.startsWith('@/'))
495
+ return join(ROOT, specifier.slice(2));
496
+ if (specifier.startsWith('.'))
497
+ return join(dirname(from), specifier);
498
+ return undefined;
499
+ }
500
+ function moduleFacts(path, text) {
501
+ const source = ts.createSourceFile(path, text, ts.ScriptTarget.Latest, true, path.endsWith('x') ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
502
+ const imported = new Set();
503
+ const exported = [];
504
+ const passedThrough = [];
505
+ const uses = [];
506
+ const use = (specifier, names) => {
507
+ if (!ts.isStringLiteralLike(specifier))
508
+ return;
509
+ const target = resolveSpecifier(path, specifier.text);
510
+ if (target !== undefined)
511
+ uses.push({ target, names });
512
+ };
513
+ const exportedAt = (name) => ({
514
+ name: name.text,
515
+ offset: name.getStart(source)
516
+ });
517
+ for (const statement of source.statements) {
518
+ if (ts.isImportDeclaration(statement)) {
519
+ const clause = statement.importClause;
520
+ const names = [];
521
+ if (clause?.name !== undefined) {
522
+ names.push('default');
523
+ imported.add(clause.name.text);
524
+ }
525
+ const bindings = clause?.namedBindings;
526
+ if (bindings !== undefined && ts.isNamespaceImport(bindings)) {
527
+ names.push(EVERYTHING);
528
+ imported.add(bindings.name.text);
529
+ }
530
+ if (bindings !== undefined && ts.isNamedImports(bindings)) {
531
+ for (const element of bindings.elements) {
532
+ names.push((element.propertyName ?? element.name).text);
533
+ imported.add(element.name.text);
534
+ }
535
+ }
536
+ use(statement.moduleSpecifier, names);
537
+ }
538
+ else if (ts.isExportDeclaration(statement)) {
539
+ const clause = statement.exportClause;
540
+ if (statement.moduleSpecifier !== undefined) {
541
+ use(statement.moduleSpecifier, clause !== undefined && ts.isNamedExports(clause)
542
+ ? clause.elements.map((element) => (element.propertyName ?? element.name).text)
543
+ : [EVERYTHING]);
544
+ if (clause !== undefined && ts.isNamedExports(clause)) {
545
+ passedThrough.push(...clause.elements.map((element) => exportedAt(element.name)));
546
+ }
547
+ else {
548
+ passedThrough.push({
549
+ name: EVERYTHING,
550
+ offset: statement.getStart(source)
551
+ });
552
+ }
553
+ }
554
+ else if (clause !== undefined && ts.isNamedExports(clause)) {
555
+ for (const element of clause.elements) {
556
+ const local = (element.propertyName ?? element.name).text;
557
+ const target = imported.has(local) ? passedThrough : exported;
558
+ target.push(exportedAt(element.name));
559
+ }
560
+ }
561
+ }
562
+ else if (ts.isExportAssignment(statement)) {
563
+ exported.push({ name: 'default', offset: statement.getStart(source) });
564
+ }
565
+ else if (isExported(statement)) {
566
+ const isDefault = ((ts.canHaveModifiers(statement) && ts.getModifiers(statement)) ||
567
+ []).some((modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword);
568
+ if (ts.isVariableStatement(statement)) {
569
+ for (const declaration of statement.declarationList.declarations) {
570
+ exported.push(...bindingNames(declaration.name).map(exportedAt));
571
+ }
572
+ }
573
+ else if (isDefault) {
574
+ exported.push({ name: 'default', offset: statement.getStart(source) });
575
+ }
576
+ else if ((ts.isFunctionDeclaration(statement) ||
577
+ ts.isClassDeclaration(statement) ||
578
+ ts.isTypeAliasDeclaration(statement) ||
579
+ ts.isInterfaceDeclaration(statement) ||
580
+ ts.isEnumDeclaration(statement)) &&
581
+ statement.name !== undefined) {
582
+ exported.push(exportedAt(statement.name));
583
+ }
584
+ }
585
+ }
586
+ const visit = (node) => {
587
+ if (ts.isCallExpression(node) &&
588
+ node.expression.kind === ts.SyntaxKind.ImportKeyword &&
589
+ node.arguments[0] !== undefined) {
590
+ use(node.arguments[0], [EVERYTHING]);
591
+ }
592
+ if (ts.isImportTypeNode(node) && ts.isLiteralTypeNode(node.argument)) {
593
+ use(node.argument.literal, [
594
+ node.qualifier?.getText(source).split('.')[0] ?? EVERYTHING
595
+ ]);
596
+ }
597
+ ts.forEachChild(node, visit);
598
+ };
599
+ visit(source);
600
+ return { exported, passedThrough, uses };
601
+ }
602
+ function exportFindings() {
603
+ const modules = new Map(ownedFiles(ROOT)
604
+ .filter((path) => MODULE_FILE.test(path))
605
+ .map((path) => [path, moduleFacts(path, readFileSync(path, 'utf8'))]));
606
+ const used = new Map();
607
+ for (const facts of modules.values()) {
608
+ for (const { target, names } of facts.uses) {
609
+ const set = used.get(target) ?? new Set();
610
+ for (const name of names)
611
+ set.add(name);
612
+ used.set(target, set);
613
+ }
614
+ }
615
+ return [...modules].flatMap(([path, facts]) => {
616
+ const kept = CONVENTION_EXPORTS[relative(ROOT, path)] ?? [];
617
+ const names = used.get(path) ?? new Set();
618
+ const isWorkerEntry = relative(ROOT, path) === WORKER_ENTRY;
619
+ return [
620
+ ...(isWorkerEntry ? [] : facts.passedThrough).map(({ offset }) => ({
621
+ path,
622
+ offset,
623
+ rule: 'no-re-exports'
624
+ })),
625
+ ...facts.exported
626
+ .filter(({ name }) => !isWorkerEntry &&
627
+ !kept.includes(name) &&
628
+ !names.has(name) &&
629
+ !names.has(EVERYTHING))
630
+ .map(({ offset }) => ({ path, offset, rule: 'no-unused-exports' }))
631
+ ];
632
+ });
633
+ }
634
+ const THEME_FILE = themeFileOf(ROOT);
635
+ const PIXELS = /^(\d+(\.\d+)?)px$/;
636
+ function themeScaleFindings() {
637
+ if (!existsSync(THEME_FILE))
638
+ return [];
639
+ const tokens = themeTokens(ROOT);
640
+ const findings = [];
641
+ const pixels = (name) => {
642
+ const match = PIXELS.exec(tokens.get(name)?.value ?? '');
643
+ return match === null ? undefined : Number(match[1]);
644
+ };
645
+ const report = (names, detail) => {
646
+ const offset = names
647
+ .map((name) => tokens.get(name)?.offset)
648
+ .find((found) => found !== undefined);
649
+ if (offset !== undefined) {
650
+ findings.push({ offset, rule: `theme-scales ${detail}` });
651
+ }
652
+ };
653
+ for (const [name, { value, offset }] of tokens) {
654
+ const [group] = name.split('.');
655
+ if (offset === undefined)
656
+ continue;
657
+ if (!pixelGroups.some((pixelGroup) => pixelGroup === group))
658
+ continue;
659
+ if (PIXELS.test(value) &&
660
+ (Number.isInteger(pixels(name)) || group === 'borders')) {
661
+ continue;
662
+ }
663
+ findings.push({
664
+ offset,
665
+ rule: `theme-scales ${name} is ${value}; write whole pixels like '28px', never em, % or calc(), so the scales can be checked against each other.`
666
+ });
667
+ }
668
+ for (const [scale, keys] of Object.entries(ascendingScales)) {
669
+ const group = scaleGroups[scale];
670
+ keys.slice(1).forEach((key, index) => {
671
+ const smaller = `${group}.${keys[index]}`;
672
+ const larger = `${group}.${key}`;
673
+ const below = pixels(smaller);
674
+ const above = pixels(larger);
675
+ if (below !== undefined && above !== undefined && above < below) {
676
+ report([larger, smaller], `${larger} (${above}px) is smaller than ${smaller} (${below}px); each step of a scale is at least as large as the one before it.`);
677
+ }
678
+ });
679
+ }
680
+ for (const { size, height, lineHeight } of textInControls) {
681
+ const control = pixels(`controls.${height}`);
682
+ const line = pixels(`lineHeights.${lineHeight}`);
683
+ const icon = pixels(`iconSizes.${size}`);
684
+ if (control !== undefined && line !== undefined && control < line) {
685
+ report([`controls.${height}`, `lineHeights.${lineHeight}`], `controls.${height} (${control}px) is smaller than lineHeights.${lineHeight} (${line}px), so text in a size ${size} button, field or menu item would clip. Raise the height or lower the line height; the two scales move together.`);
686
+ }
687
+ if (control !== undefined && icon !== undefined && icon > control) {
688
+ report([`iconSizes.${size}`, `controls.${height}`], `iconSizes.${size} (${icon}px) is larger than controls.${height} (${control}px), so the icon would overflow a size ${size} control.`);
689
+ }
690
+ }
691
+ for (const { lineHeight, fontSize } of textLines) {
692
+ const line = pixels(`lineHeights.${lineHeight}`);
693
+ const font = pixels(`fontSizes.${fontSize}`);
694
+ if (line !== undefined && font !== undefined && line < font) {
695
+ report([`lineHeights.${lineHeight}`, `fontSizes.${fontSize}`], `lineHeights.${lineHeight} (${line}px) is smaller than fontSizes.${fontSize} (${font}px), so lines of that text would overlap. Re-value them as a pair.`);
696
+ }
697
+ }
698
+ return findings;
699
+ }
700
+ const findingLines = [];
701
+ const violatedRules = new Set();
702
+ let scannedCount = 0;
703
+ for (const path of ownedFiles(ROOT)) {
704
+ const text = readFileSync(path, 'utf8');
705
+ const scanner = scannerFor(path, text);
706
+ if (scanner === undefined)
707
+ continue;
708
+ scannedCount += 1;
709
+ for (const finding of scanner(text)) {
710
+ violatedRules.add(finding.rule);
711
+ findingLines.push(`${relative(ROOT, path)}:${lineAndColumn(text, finding.offset)}: pattern(${finding.rule})`);
712
+ }
713
+ }
714
+ for (const { path, offset, rule } of exportFindings()) {
715
+ violatedRules.add(rule);
716
+ findingLines.push(`${relative(ROOT, path)}:${lineAndColumn(readFileSync(path, 'utf8'), offset)}: pattern(${rule})`);
717
+ }
718
+ for (const finding of themeScaleFindings()) {
719
+ violatedRules.add('theme-scales');
720
+ findingLines.push(`${relative(ROOT, THEME_FILE)}:${lineAndColumn(readFileSync(THEME_FILE, 'utf8'), finding.offset)}: pattern(${finding.rule})`);
721
+ }
722
+ for (const path of misplacedSourceFiles()) {
723
+ violatedRules.add('src-structure');
724
+ findingLines.push(`${relative(ROOT, path)}:1:1: pattern(src-structure)`);
725
+ }
726
+ for (const { offset, fix } of schemaFindings()) {
727
+ violatedRules.add('schema-tables');
728
+ findingLines.push(`schema.ts:${lineAndColumn(readFileSync(SCHEMA_FILE, 'utf8'), offset)}: pattern(schema-tables) add: ${fix}`);
729
+ }
730
+ if (!runsAccessFlows()) {
731
+ violatedRules.add('access-flows');
732
+ findingLines.push(`${relative(ROOT, ACCESS_FLOWS_FILE)}:1:1: pattern(access-flows)`);
733
+ }
734
+ if (existsSync(SRC_DIRECTORY) && toasterPlacements() !== 1) {
735
+ violatedRules.add('toaster-once');
736
+ findingLines.push('src/layouts/AppLayout.tsx:1:1: pattern(toaster-once)');
737
+ }
738
+ for (const path of unwiredScripts()) {
739
+ violatedRules.add('scripts-wired');
740
+ findingLines.push(`${relative(ROOT, path)}:1:1: pattern(scripts-wired)`);
741
+ }
742
+ if (findingLines.length > 0) {
743
+ const guidance = [...violatedRules].map((rule) => `pattern(${rule}): ${MESSAGES[rule] ?? rule}`);
744
+ process.stdout.write(`${[...findingLines, '', ...guidance].join('\n')}\n`);
745
+ process.exitCode = 1;
746
+ }
747
+ else {
748
+ process.stdout.write(`No Pattern source rule violations in ${scannedCount} files\n`);
749
+ }