@astryxdesign/cli 0.6.3-canary.db4e378 → 0.6.3-canary.db7b338

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 (276) hide show
  1. package/README.md +22 -1
  2. package/api/blog/blog.doc.mjs +1 -0
  3. package/api/build/build.doc.mjs +1 -0
  4. package/api/component/component.doc.mjs +1 -0
  5. package/api/discover/discover.doc.mjs +1 -0
  6. package/api/docs/_adapter.d.mts +3 -8
  7. package/api/docs/_adapter.mjs +13 -105
  8. package/api/docs/detail/section/section.mjs +9 -15
  9. package/api/docs/detail/section/section.test.mjs +15 -6
  10. package/api/docs/docs.doc.mjs +1 -0
  11. package/api/docs/list/list.mjs +3 -7
  12. package/api/doctor/doctor.d.mts +25 -3
  13. package/api/doctor/doctor.doc.mjs +1 -0
  14. package/api/doctor/doctor.mjs +186 -14
  15. package/api/doctor/doctor.test.mjs +332 -7
  16. package/api/gap-report/gap-report.doc.mjs +1 -0
  17. package/api/hook/_adapter.mjs +19 -5
  18. package/api/hook/hook.doc.mjs +1 -0
  19. package/api/hook/list/list.d.mts +1 -1
  20. package/api/hook/list/list.mjs +69 -17
  21. package/api/init/init.doc.mjs +1 -0
  22. package/api/integration/add-contribution.mjs +7 -7
  23. package/api/integration/add-contribution.test.mjs +37 -3
  24. package/api/integration/add-theme.mjs +34 -64
  25. package/api/integration/add-theme.test.mjs +105 -21
  26. package/api/integration/authoring-checks.test.mjs +8 -4
  27. package/api/integration/integration-block-exports.test.mjs +10 -6
  28. package/api/integration/integrationAdd.doc.mjs +1 -0
  29. package/api/integration/integrationAddAgentDoc.doc.mjs +1 -0
  30. package/api/integration/integrationAddCodemod.doc.mjs +1 -0
  31. package/api/integration/integrationAddComponent.doc.mjs +1 -0
  32. package/api/integration/integrationAddDoc.doc.mjs +1 -0
  33. package/api/integration/integrationAddTemplate.doc.mjs +1 -0
  34. package/api/integration/integrationAddTheme.doc.mjs +6 -5
  35. package/api/integration/integrationComponentConflicts.doc.mjs +1 -0
  36. package/api/integration/integrationDocConflicts.doc.mjs +1 -0
  37. package/api/integration/integrationPackCheck.doc.mjs +1 -0
  38. package/api/integration/integrationTemplateConflicts.doc.mjs +1 -0
  39. package/api/integration/pack-check.test.mjs +17 -47
  40. package/api/integration/summarizeIssues.doc.mjs +1 -0
  41. package/api/integration/validate-integration-fixes.test.mjs +1389 -0
  42. package/api/integration/validate-integration.mjs +50 -102
  43. package/api/integration/validate-integration.test.mjs +85 -23
  44. package/api/integration/validate-unread-theme-folders.test.mjs +110 -0
  45. package/api/integration/validateIntegration.doc.mjs +1 -0
  46. package/api/json/assertResponse.doc.mjs +1 -0
  47. package/api/json/isError.doc.mjs +1 -0
  48. package/api/json/parseResponse.doc.mjs +1 -0
  49. package/api/layout/layoutCheck.doc.mjs +1 -0
  50. package/api/layout/layoutExpand.doc.mjs +1 -0
  51. package/api/layout/layoutGrammar.doc.mjs +1 -0
  52. package/api/search/search.doc.mjs +1 -0
  53. package/api/search/search.mjs +16 -6
  54. package/api/swizzle/swizzle.doc.mjs +1 -0
  55. package/api/template/template-suffix.test.mjs +41 -21
  56. package/api/template/template.doc.mjs +1 -0
  57. package/api/theme/_adapter.d.mts +2 -3
  58. package/api/theme/_adapter.mjs +4 -5
  59. package/api/theme/add/add.binary.test.mjs +10 -17
  60. package/api/theme/add/add.test.mjs +14 -1
  61. package/api/theme/generateTonalPalette.doc.mjs +1 -0
  62. package/api/theme/integration-themes.test.mjs +39 -28
  63. package/api/theme/list/list.test.mjs +19 -20
  64. package/api/theme/listThemes.doc.mjs +6 -5
  65. package/api/theme/themeAdd.doc.mjs +4 -3
  66. package/api/theme/themeBuild.doc.mjs +1 -0
  67. package/api/theme/themeList.doc.mjs +6 -3
  68. package/api/theme/themeListAvailable.doc.mjs +5 -3
  69. package/api/theme/themePaletteGenerate.doc.mjs +1 -0
  70. package/api/theme/themeTargets.doc.mjs +1 -0
  71. package/api/theme/themeTemplate.doc.mjs +1 -0
  72. package/api/upgrade/_adapter.d.mts +32 -5
  73. package/api/upgrade/_adapter.mjs +97 -69
  74. package/api/upgrade/provider-agreement.test.mjs +152 -0
  75. package/api/upgrade/run/run.mjs +356 -59
  76. package/api/upgrade/upgrade.doc.mjs +6 -1
  77. package/api/upgrade/upgrade.type.d.mts +34 -0
  78. package/api/upgrade/upgrade.type.mjs +15 -0
  79. package/assets/codemods/__tests__/runner.test.mjs +330 -8
  80. package/assets/codemods/integration-discovery.mjs +8 -2
  81. package/assets/codemods/integration-discovery.test.mjs +15 -0
  82. package/assets/codemods/integration-runner.mjs +56 -4
  83. package/assets/codemods/integration-runner.protection.test.mjs +153 -0
  84. package/assets/codemods/run-codemod.mjs +177 -34
  85. package/assets/codemods/runner.mjs +350 -102
  86. package/assets/codemods/transforms/next/__tests__/migrate-native-picker-to-presentation.test.mjs +63 -0
  87. package/assets/codemods/transforms/next/__tests__/migrate-theme-catalog-to-descriptors.test.mjs +220 -0
  88. package/assets/codemods/transforms/next/index.mjs +19 -1
  89. package/assets/codemods/transforms/next/migrate-native-picker-to-presentation.mjs +148 -0
  90. package/assets/codemods/transforms/next/migrate-theme-catalog-to-descriptors.mjs +141 -0
  91. package/assets/docs/cli-integrations.doc.mjs +30 -30
  92. package/assets/docs/cli.doc.mjs +15 -0
  93. package/assets/docs/theme.doc.mjs +1 -1
  94. package/assets/templates/blocks/components/DateInput/DateInputDateRange.tsx +1 -1
  95. package/assets/templates/blocks/components/Item/ItemDocumentTabs.doc.mjs +14 -0
  96. package/assets/templates/blocks/components/Item/ItemDocumentTabs.tsx +100 -0
  97. package/assets/templates/blocks/components/TimeInput/TimeInputConstrained.tsx +1 -0
  98. package/assets/templates/themes/butter/butterTheme.doc.mjs +11 -0
  99. package/assets/templates/themes/chocolate/chocolateTheme.doc.mjs +11 -0
  100. package/assets/templates/themes/gothic/gothicTheme.doc.mjs +11 -0
  101. package/assets/templates/themes/matcha/matchaTheme.doc.mjs +11 -0
  102. package/assets/templates/themes/neutral/neutralTheme.doc.mjs +11 -0
  103. package/assets/templates/themes/stone/stoneTheme.doc.mjs +11 -0
  104. package/assets/templates/themes/y2k/y2kTheme.doc.mjs +11 -0
  105. package/authoring/codemod/codemod.doc.mjs +1 -1
  106. package/authoring/codemod/type.ts +12 -0
  107. package/authoring/config/config.doc.mjs +1 -1
  108. package/authoring/debug/debug.doc.d.mts +11 -0
  109. package/authoring/debug/debug.doc.mjs +182 -0
  110. package/authoring/doctypes/_schema.d.mts +71 -141
  111. package/authoring/doctypes/_schema.mjs +29 -2
  112. package/authoring/doctypes/base/graph-fields.doc.mjs +1 -1
  113. package/authoring/doctypes/command/command.doc.mjs +1 -1
  114. package/authoring/doctypes/command/type.ts +1 -1
  115. package/authoring/doctypes/component/type.ts +2 -2
  116. package/authoring/doctypes/doctypes-new.test.mjs +48 -6
  117. package/authoring/doctypes/enum/enum.doc.mjs +1 -1
  118. package/authoring/doctypes/enum/type.ts +1 -1
  119. package/authoring/doctypes/function/function.doc.mjs +1 -1
  120. package/authoring/doctypes/function/type.ts +1 -1
  121. package/authoring/doctypes/hook/type.ts +2 -2
  122. package/authoring/doctypes/load-contract.test.mjs +3 -2
  123. package/authoring/doctypes/namespace/namespace.doc.mjs +2 -2
  124. package/authoring/doctypes/namespace/parse.test.mjs +23 -25
  125. package/authoring/doctypes/namespace/type.ts +5 -2
  126. package/authoring/doctypes/parse.d.mts +4 -2
  127. package/authoring/doctypes/parse.mjs +10 -5
  128. package/authoring/doctypes/reference/reference.doc.mjs +6 -4
  129. package/authoring/doctypes/reference/type.ts +9 -10
  130. package/authoring/doctypes/schema/schema.doc.mjs +1 -1
  131. package/authoring/doctypes/schema/type.ts +1 -2
  132. package/authoring/doctypes/template/template.doc.mjs +3 -3
  133. package/authoring/doctypes/theme/parse.d.mts +35 -0
  134. package/authoring/doctypes/theme/parse.mjs +76 -0
  135. package/authoring/doctypes/theme/theme.doc.d.mts +9 -0
  136. package/authoring/doctypes/theme/theme.doc.mjs +79 -0
  137. package/authoring/doctypes/theme/type.ts +42 -0
  138. package/authoring/doctypes/types.ts +2 -1
  139. package/authoring/gap-report/gap-report.doc.d.mts +12 -0
  140. package/authoring/gap-report/gap-report.doc.mjs +183 -0
  141. package/authoring/index.d.mts +1 -0
  142. package/authoring/index.d.ts +7 -4
  143. package/authoring/index.mjs +2 -1
  144. package/authoring/integration/integration.doc.mjs +2 -2
  145. package/authoring/integration/type.ts +3 -9
  146. package/clients/cli/__tests__/cliManifest.test.ts +27 -29
  147. package/clients/cli/commands/blog.doc.mjs +1 -1
  148. package/clients/cli/commands/build-theme.adaptations.test.mjs +100 -1
  149. package/clients/cli/commands/build-theme.mjs +10 -44
  150. package/clients/cli/commands/build.doc.mjs +1 -1
  151. package/clients/cli/commands/component.doc.mjs +1 -1
  152. package/clients/cli/commands/discover.doc.mjs +1 -1
  153. package/clients/cli/commands/docs.doc.mjs +1 -1
  154. package/clients/cli/commands/docs.mjs +15 -58
  155. package/clients/cli/commands/docs.test.mjs +11 -14
  156. package/clients/cli/commands/doctor-integration-components.doc.mjs +1 -1
  157. package/clients/cli/commands/doctor-integration-docs.doc.mjs +1 -1
  158. package/clients/cli/commands/doctor-integration-templates.doc.mjs +1 -1
  159. package/clients/cli/commands/doctor-integration-validate.doc.mjs +1 -1
  160. package/clients/cli/commands/doctor-integration.doc.mjs +1 -1
  161. package/clients/cli/commands/doctor-integration.test.mjs +15 -7
  162. package/clients/cli/commands/doctor.doc.mjs +1 -1
  163. package/clients/cli/commands/gap-report.doc.mjs +1 -1
  164. package/clients/cli/commands/hook.doc.mjs +1 -1
  165. package/clients/cli/commands/init.doc.mjs +1 -1
  166. package/clients/cli/commands/integration-add.controls.test.mjs +1 -1
  167. package/clients/cli/commands/integration-add.doc.mjs +1 -1
  168. package/clients/cli/commands/integration-pack.doc.mjs +1 -1
  169. package/clients/cli/commands/integration-real-world.test.mjs +3 -9
  170. package/clients/cli/commands/integration.doc.mjs +1 -1
  171. package/clients/cli/commands/layout-check.doc.mjs +1 -1
  172. package/clients/cli/commands/layout-expand.doc.mjs +1 -1
  173. package/clients/cli/commands/layout-grammar.doc.mjs +1 -1
  174. package/clients/cli/commands/layout.doc.mjs +1 -1
  175. package/clients/cli/commands/manifest.doc.mjs +1 -1
  176. package/clients/cli/commands/search.doc.mjs +1 -1
  177. package/clients/cli/commands/swizzle.doc.mjs +1 -1
  178. package/clients/cli/commands/template.doc.mjs +1 -1
  179. package/clients/cli/commands/text-json-parity.test.mjs +718 -0
  180. package/clients/cli/commands/theme-add.doc.mjs +2 -2
  181. package/clients/cli/commands/theme-build.doc.mjs +1 -1
  182. package/clients/cli/commands/theme-list.doc.mjs +2 -2
  183. package/clients/cli/commands/theme-palette-generate.doc.mjs +3 -3
  184. package/clients/cli/commands/theme-palette.doc.mjs +1 -1
  185. package/clients/cli/commands/theme-targets.behavior.test.mjs +4 -3
  186. package/clients/cli/commands/theme-targets.doc.mjs +1 -1
  187. package/clients/cli/commands/theme-template.doc.mjs +1 -1
  188. package/clients/cli/commands/theme.doc.mjs +1 -1
  189. package/clients/cli/commands/upgrade.doc.mjs +3 -2
  190. package/clients/cli/commands/upgrade.file-protection.test.mjs +228 -0
  191. package/clients/cli/commands/upgrade.mjs +29 -7
  192. package/clients/cli/formatters/index.mjs +2 -0
  193. package/clients/cli/formatters/index.test.mjs +6 -0
  194. package/clients/cli/lib/hook-format.mjs +14 -5
  195. package/foundation/config/project-themes.test.mjs +11 -19
  196. package/foundation/config/project.d.mts +8 -0
  197. package/foundation/config/project.mjs +50 -21
  198. package/foundation/config/project.test.mjs +3 -16
  199. package/foundation/discovery/authoring-self-docs.d.mts +6 -0
  200. package/foundation/discovery/authoring-self-docs.mjs +17 -7
  201. package/foundation/discovery/authoring-self-docs.test.mjs +3 -2
  202. package/foundation/discovery/authoring-surface.d.mts +74 -0
  203. package/foundation/discovery/authoring-surface.mjs +525 -0
  204. package/foundation/discovery/authoring-surface.test.mjs +392 -0
  205. package/foundation/discovery/cli-self-docs.d.mts +113 -0
  206. package/foundation/discovery/cli-self-docs.mjs +514 -0
  207. package/foundation/discovery/cli-self-docs.test.mjs +437 -0
  208. package/foundation/discovery/component-loader.d.mts +35 -38
  209. package/foundation/discovery/component-loader.mjs +53 -222
  210. package/foundation/discovery/docs-discovery.d.mts +3 -2
  211. package/foundation/discovery/docs-discovery.mjs +14 -17
  212. package/foundation/discovery/docs-discovery.test.mjs +15 -15
  213. package/foundation/discovery/docs-section-key.d.mts +19 -8
  214. package/foundation/discovery/docs-section-key.mjs +119 -32
  215. package/foundation/discovery/docs-section-key.test.mjs +41 -19
  216. package/foundation/discovery/template-adapter.d.mts +7 -0
  217. package/foundation/discovery/template-adapter.mjs +27 -40
  218. package/foundation/discovery/template-adapter.test.mjs +15 -0
  219. package/foundation/discovery/theme-discovery.d.mts +67 -7
  220. package/foundation/discovery/theme-discovery.mjs +916 -186
  221. package/foundation/discovery/theme-discovery.test.mjs +613 -219
  222. package/foundation/doc-compiler/bundle.d.mts +47 -0
  223. package/foundation/doc-compiler/bundle.mjs +218 -0
  224. package/foundation/doc-compiler/bundle.test.mjs +255 -0
  225. package/foundation/doc-compiler/compile.d.mts +200 -0
  226. package/foundation/doc-compiler/compile.mjs +259 -9
  227. package/foundation/doc-compiler/diagnostics.d.mts +126 -0
  228. package/foundation/doc-compiler/diagnostics.mjs +277 -0
  229. package/foundation/doc-compiler/doc-compiler.test.mjs +28 -1
  230. package/foundation/doc-compiler/doc-loads.test.mjs +1642 -0
  231. package/foundation/doc-compiler/import.d.mts +24 -0
  232. package/foundation/doc-compiler/import.mjs +59 -0
  233. package/foundation/doc-compiler/inputs.d.mts +102 -0
  234. package/foundation/doc-compiler/inputs.mjs +286 -0
  235. package/foundation/doc-compiler/inputs.test.mjs +299 -0
  236. package/foundation/doc-compiler/ir.d.mts +13 -0
  237. package/foundation/doc-compiler/ir.mjs +189 -12
  238. package/foundation/doc-compiler/lower-doc.test.mjs +492 -0
  239. package/foundation/doc-compiler/overlays.d.mts +37 -0
  240. package/foundation/doc-compiler/overlays.mjs +206 -0
  241. package/foundation/doc-compiler/parse-readable.d.mts +9 -0
  242. package/foundation/doc-compiler/parse-readable.mjs +29 -0
  243. package/foundation/doc-compiler/read.d.mts +126 -0
  244. package/foundation/doc-compiler/read.mjs +320 -0
  245. package/foundation/doc-compiler/read.test.mjs +313 -0
  246. package/foundation/doc-compiler/source.d.mts +33 -0
  247. package/foundation/doc-compiler/source.mjs +128 -0
  248. package/foundation/fs/file-protection.d.mts +33 -0
  249. package/foundation/fs/file-protection.mjs +825 -0
  250. package/foundation/fs/file-protection.test.mjs +250 -0
  251. package/foundation/integrations/autolink.d.mts +58 -1
  252. package/foundation/integrations/autolink.mjs +143 -57
  253. package/foundation/integrations/contribution-fixes.d.mts +145 -0
  254. package/foundation/integrations/contribution-fixes.mjs +1284 -0
  255. package/foundation/integrations/contribution-inventory.d.mts +1 -1
  256. package/foundation/integrations/contribution-inventory.mjs +17 -20
  257. package/foundation/integrations/contribution-inventory.test.mjs +67 -27
  258. package/foundation/integrations/integrations.d.mts +3 -0
  259. package/foundation/integrations/integrations.mjs +11 -97
  260. package/foundation/integrations/provider-ledger.test.mjs +275 -0
  261. package/foundation/integrations/provider-resolution.d.mts +152 -0
  262. package/foundation/integrations/provider-resolution.mjs +576 -0
  263. package/foundation/integrations/provider-resolution.test.mjs +369 -0
  264. package/foundation/integrations/theme-descriptor.d.mts +8 -0
  265. package/foundation/integrations/theme-descriptor.mjs +44 -0
  266. package/foundation/integrations/validate-contributions.mjs +104 -10
  267. package/foundation/response/error-codes.d.mts +3 -1
  268. package/foundation/response/error-codes.d.ts +2 -0
  269. package/foundation/response/error-codes.doc.mjs +12 -2
  270. package/foundation/response/error-codes.mjs +7 -1
  271. package/foundation/response/error-codes.test.mjs +55 -11
  272. package/foundation/response/response-types.doc.mjs +1 -1
  273. package/foundation/response/response.doc.mjs +1 -1
  274. package/foundation/text/string-utils.mjs +22 -10
  275. package/package.json +10 -9
  276. package/assets/templates/themes/manifest.json +0 -95
@@ -0,0 +1,825 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file VCS-neutral protection for existing consumer files.
5
+ *
6
+ * Reads declarations from the working tree only. No Git, Sapling, or Mercurial
7
+ * process is invoked, so the same checkout bytes produce the same answer in
8
+ * every supported environment.
9
+ */
10
+
11
+ import * as fs from 'node:fs';
12
+ import * as path from 'node:path';
13
+ import ignore from 'ignore';
14
+
15
+ const DEPENDENCY_DIRS = new Set([
16
+ 'node_modules',
17
+ 'bower_components',
18
+ 'jspm_packages',
19
+ ]);
20
+ const YARN_DEPENDENCY_DIRS = new Set([
21
+ '__virtual__',
22
+ 'cache',
23
+ 'sdks',
24
+ 'unplugged',
25
+ ]);
26
+ const YARN_DEPENDENCY_FILES = new Set(['install-state.gz']);
27
+ const VCS_DIRS = new Set(['.git', '.hg', '.sl']);
28
+ const PROTECTION_FILES = new Set(['.gitattributes', '.gitignore', '.hgignore']);
29
+
30
+ /** A stable failure raised before any consumer write. */
31
+ export class ProtectionSourceError extends Error {
32
+ /** @param {string} source @param {string} reason */
33
+ constructor(source, reason) {
34
+ super(`Could not read file-protection source ${source}: ${reason}`);
35
+ this.name = 'ProtectionSourceError';
36
+ this.code = 'ERR_CODEMOD_PROTECTION_SOURCE';
37
+ this.source = source;
38
+ }
39
+ }
40
+
41
+ /** @param {string} value */
42
+ function slash(value) {
43
+ return value.split(path.sep).join('/');
44
+ }
45
+
46
+ /** @param {string} candidate @param {string} root */
47
+ function relativeWithin(candidate, root) {
48
+ const relative = path.relative(root, candidate);
49
+ if (
50
+ relative === '' ||
51
+ (!relative.startsWith(`..${path.sep}`) &&
52
+ relative !== '..' &&
53
+ !path.isAbsolute(relative))
54
+ ) {
55
+ return slash(relative);
56
+ }
57
+ return null;
58
+ }
59
+
60
+ /**
61
+ * Translate the path globs used by attributes and Mercurial into a RegExp.
62
+ * `*` and `?` never cross a path separator; `**` does.
63
+ * @param {string} pattern
64
+ * @param {{basename?: boolean}} [options]
65
+ */
66
+ function globRegex(pattern, {basename = false} = {}) {
67
+ let out = basename ? '(?:^|/)' : '^';
68
+ let i = 0;
69
+ while (i < pattern.length) {
70
+ const c = pattern[i];
71
+ if (c === '*') {
72
+ if (pattern[i + 1] === '*') {
73
+ while (pattern[i + 1] === '*') i++;
74
+ if (pattern[i + 1] === '/') {
75
+ i++;
76
+ out += '(?:.*/)?';
77
+ } else {
78
+ out += '.*';
79
+ }
80
+ } else {
81
+ out += '[^/]*';
82
+ }
83
+ } else if (c === '?') {
84
+ out += '[^/]';
85
+ } else if (c === '[') {
86
+ const end = pattern.indexOf(']', i + 1);
87
+ if (end === -1) throw new Error('unterminated character class');
88
+ let cls = pattern.slice(i + 1, end);
89
+ if (cls.startsWith('!')) cls = `^${cls.slice(1)}`;
90
+ out += `[${cls.replace(/\\/g, '\\\\')}]`;
91
+ i = end;
92
+ } else if (c === '\\' && i + 1 < pattern.length) {
93
+ i++;
94
+ out += pattern[i].replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
95
+ } else {
96
+ out += c.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
97
+ }
98
+ i++;
99
+ }
100
+ out += '$';
101
+ return new RegExp(out);
102
+ }
103
+
104
+ /** @param {string} line */
105
+ function splitAttributeLine(line) {
106
+ /** @type {string[]} */
107
+ const tokens = [];
108
+ let token = '';
109
+ let escaped = false;
110
+ let quoted = false;
111
+ const text = line.trim();
112
+ for (let index = 0; index < text.length; index++) {
113
+ const c = text[index];
114
+ if (escaped) {
115
+ const octal = quoted ? /^[0-7]{1,3}/.exec(text.slice(index)) : null;
116
+ if (octal) {
117
+ token += String.fromCodePoint(Number.parseInt(octal[0], 8));
118
+ index += octal[0].length - 1;
119
+ } else if (quoted) {
120
+ token += c === 'n' ? '\n' : c === 't' ? '\t' : c === 'r' ? '\r' : c;
121
+ } else {
122
+ // Keep pattern escapes so globRegex can distinguish a literal wildcard
123
+ // from a wildcard. Escaped whitespace is only tokenization syntax.
124
+ token += /\s/.test(c) ? c : `\\${c}`;
125
+ }
126
+ escaped = false;
127
+ } else if (c === '\\') {
128
+ escaped = true;
129
+ } else if (c === '"') {
130
+ quoted = !quoted;
131
+ } else if (/\s/.test(c) && !quoted) {
132
+ if (token) {
133
+ tokens.push(token);
134
+ token = '';
135
+ }
136
+ } else {
137
+ token += c;
138
+ }
139
+ }
140
+ if (escaped) token += '\\';
141
+ if (quoted) throw new Error('unterminated quoted pattern');
142
+ if (token) tokens.push(token);
143
+ return tokens;
144
+ }
145
+
146
+ /**
147
+ * @param {string[]} tokens
148
+ * @param {Map<string, Map<string, boolean|null>>} macros
149
+ */
150
+ function parseAttributeAssignments(tokens, macros) {
151
+ /** @type {Map<string, boolean|null>} */
152
+ const attributes = new Map();
153
+ for (const token of tokens) {
154
+ const macro = macros.get(token);
155
+ if (macro) {
156
+ for (const [name, value] of macro) attributes.set(name, value);
157
+ continue;
158
+ }
159
+ const decorated = token.startsWith('-') || token.startsWith('!');
160
+ const decoratedName = decorated ? token.slice(1) : token;
161
+ if (token.startsWith('-')) attributes.set(decoratedName, false);
162
+ else if (token.startsWith('!')) attributes.set(decoratedName, null);
163
+ else {
164
+ const equals = token.indexOf('=');
165
+ const name = equals === -1 ? token : token.slice(0, equals);
166
+ const value = equals === -1 ? 'true' : token.slice(equals + 1);
167
+ attributes.set(name, value.toLowerCase() !== 'false');
168
+ }
169
+ }
170
+ return attributes;
171
+ }
172
+
173
+ /**
174
+ * @param {string} source
175
+ * @param {string} contents
176
+ * @param {Map<string, Map<string, boolean|null>>} macros
177
+ * @param {boolean} topLevel
178
+ * @returns {Array<{pattern: RegExp, attributes: Map<string, boolean|null>, line: number}>}
179
+ */
180
+ function parseAttributes(source, contents, macros, topLevel) {
181
+ const rules = [];
182
+ const lines = contents.split(/\r?\n/);
183
+ if (topLevel) {
184
+ /** @type {Map<string, {tokens: string[], line: number}>} */
185
+ const definitions = new Map();
186
+ for (let index = 0; index < lines.length; index++) {
187
+ const raw = lines[index].trim();
188
+ if (!raw || raw.startsWith('#')) continue;
189
+ let tokens;
190
+ try {
191
+ tokens = splitAttributeLine(raw);
192
+ } catch (error) {
193
+ throw new ProtectionSourceError(
194
+ source,
195
+ `invalid rule on line ${index + 1}: ${error instanceof Error ? error.message : String(error)}`,
196
+ );
197
+ }
198
+ const pattern = tokens.shift();
199
+ if (!pattern?.startsWith('[attr]')) continue;
200
+ const name = pattern.slice('[attr]'.length);
201
+ if (!name || tokens.length === 0) {
202
+ throw new ProtectionSourceError(
203
+ source,
204
+ `invalid attribute macro on line ${index + 1}`,
205
+ );
206
+ }
207
+ definitions.set(name, {tokens, line: index + 1});
208
+ }
209
+ const resolving = new Set();
210
+ /** @param {string} name */
211
+ const resolveMacro = name => {
212
+ if (macros.has(name)) return macros.get(name);
213
+ const definition = definitions.get(name);
214
+ if (!definition) return undefined;
215
+ if (resolving.has(name)) {
216
+ throw new ProtectionSourceError(
217
+ source,
218
+ `cyclic attribute macro on line ${definition.line}`,
219
+ );
220
+ }
221
+ resolving.add(name);
222
+ for (const token of definition.tokens) {
223
+ if (definitions.has(token)) resolveMacro(token);
224
+ }
225
+ const attributes = parseAttributeAssignments(definition.tokens, macros);
226
+ macros.set(name, attributes);
227
+ resolving.delete(name);
228
+ return attributes;
229
+ };
230
+ for (const name of definitions.keys()) resolveMacro(name);
231
+ }
232
+ for (let index = 0; index < lines.length; index++) {
233
+ const raw = lines[index].trim();
234
+ if (!raw || raw.startsWith('#')) continue;
235
+ let tokens;
236
+ try {
237
+ tokens = splitAttributeLine(raw);
238
+ } catch (error) {
239
+ throw new ProtectionSourceError(
240
+ source,
241
+ `invalid rule on line ${index + 1}: ${error instanceof Error ? error.message : String(error)}`,
242
+ );
243
+ }
244
+ if (tokens.length < 2) {
245
+ throw new ProtectionSourceError(
246
+ source,
247
+ `invalid rule on line ${index + 1}`,
248
+ );
249
+ }
250
+ let pattern = tokens.shift();
251
+ if (!pattern || pattern.startsWith('!')) {
252
+ throw new ProtectionSourceError(
253
+ source,
254
+ `invalid pattern on line ${index + 1}`,
255
+ );
256
+ }
257
+ if (pattern.startsWith('[attr]')) {
258
+ const name = pattern.slice('[attr]'.length);
259
+ if (!topLevel || !name) {
260
+ throw new ProtectionSourceError(
261
+ source,
262
+ `invalid attribute macro on line ${index + 1}`,
263
+ );
264
+ }
265
+ continue;
266
+ }
267
+ pattern = pattern.replace(/^\//, '');
268
+ const basename = !pattern.includes('/');
269
+ let matcher;
270
+ try {
271
+ matcher = globRegex(pattern, {basename});
272
+ } catch (error) {
273
+ throw new ProtectionSourceError(
274
+ source,
275
+ `invalid pattern on line ${index + 1}: ${error instanceof Error ? error.message : String(error)}`,
276
+ );
277
+ }
278
+ const attributes = parseAttributeAssignments(tokens, macros);
279
+ rules.push({pattern: matcher, attributes, line: index + 1});
280
+ }
281
+ return rules;
282
+ }
283
+
284
+ /** @param {string} contents @param {string} filePath */
285
+ function generatedMarker(contents, filePath) {
286
+ const text = contents.slice(0, 65_536);
287
+ const extension = path.extname(filePath).toLowerCase();
288
+ const hashComments =
289
+ text.startsWith('#!') ||
290
+ new Set([
291
+ '.py',
292
+ '.pyi',
293
+ '.rb',
294
+ '.sh',
295
+ '.bash',
296
+ '.zsh',
297
+ '.yaml',
298
+ '.yml',
299
+ '.toml',
300
+ ]).has(extension);
301
+ const dashComments = new Set(['.sql', '.lua']).has(extension);
302
+ let cursor = 0;
303
+ if (text.startsWith('#!')) {
304
+ const end = text.indexOf('\n');
305
+ cursor = end === -1 ? text.length : end + 1;
306
+ }
307
+ /** @type {string[]} */
308
+ const comments = [];
309
+ while (cursor < text.length) {
310
+ const ws = /^\s+/.exec(text.slice(cursor));
311
+ if (ws) cursor += ws[0].length;
312
+ if (
313
+ text.startsWith('//', cursor) ||
314
+ (hashComments && text.startsWith('#', cursor)) ||
315
+ (dashComments && text.startsWith('--', cursor))
316
+ ) {
317
+ const end = text.indexOf('\n', cursor);
318
+ comments.push(text.slice(cursor, end === -1 ? text.length : end));
319
+ cursor = end === -1 ? text.length : end + 1;
320
+ continue;
321
+ }
322
+ const block = text.startsWith('/*', cursor)
323
+ ? ['/*', '*/']
324
+ : text.startsWith('<!--', cursor)
325
+ ? ['<!--', '-->']
326
+ : null;
327
+ if (block) {
328
+ const end = text.indexOf(block[1], cursor + block[0].length);
329
+ if (end === -1) break;
330
+ comments.push(text.slice(cursor, end + block[1].length));
331
+ cursor = end + block[1].length;
332
+ continue;
333
+ }
334
+ break;
335
+ }
336
+ const leading = comments.join('\n');
337
+ if (/(?:^|[^\w-])@partially-generated(?:[^\w-]|$)/i.test(leading))
338
+ return '@partially-generated in the leading comment block';
339
+ if (/(?:^|[^\w-])@generated(?:[^\w-]|$)/i.test(leading))
340
+ return '@generated in the leading comment block';
341
+ if (/Code generated\b[^\r\n]*\bDO NOT EDIT\./.test(leading))
342
+ return 'Code generated ... DO NOT EDIT. in the leading comment block';
343
+ return null;
344
+ }
345
+
346
+ /** @param {string} contents */
347
+ function generatedCommand(contents) {
348
+ const match = contents
349
+ .slice(0, 65_536)
350
+ .match(/^\s*(?:\/\/|\*|#|<!--)?\s*Command:\s*(.+?)\s*(?:\*\/|-->)?\s*$/m);
351
+ return match?.[1]?.trim() || undefined;
352
+ }
353
+
354
+ /**
355
+ * @typedef {{file: string, reason: 'generated'|'vendored'|'ignored'|'dependency'|'vcs'|'symlink'|'outside-root', declaration: string, source?: string, line?: number, command?: string}} FileProtection
356
+ */
357
+
358
+ /**
359
+ * Create an eager, fail-closed resolver. Every declaration under root is parsed
360
+ * before the caller can begin writing consumer files.
361
+ * @param {string} root
362
+ * @param {{overrides?: Map<string, string|null>}} [options]
363
+ */
364
+ export function createFileProtectionResolver(
365
+ root,
366
+ {overrides = new Map()} = {},
367
+ ) {
368
+ const resolvedRoot = path.resolve(root);
369
+ const normalizedOverrides = new Map(
370
+ [...overrides].map(([file, contents]) => [path.resolve(file), contents]),
371
+ );
372
+ /** @type {string} */
373
+ let realRoot;
374
+ try {
375
+ realRoot = fs.realpathSync(resolvedRoot);
376
+ } catch (error) {
377
+ throw new ProtectionSourceError(
378
+ '.',
379
+ error instanceof Error ? error.message : String(error),
380
+ );
381
+ }
382
+ /** @type {Array<{file: string, dir: string, contents: string}>} */
383
+ const rawAttributeSources = [];
384
+ /** @type {Array<{file: string, dir: string, rules: ReturnType<typeof parseAttributes>}>} */
385
+ const attributeSources = [];
386
+ /** @type {Map<string, Map<string, boolean|null>>} */
387
+ const attributeMacros = new Map();
388
+ /** @type {Array<{file: string, dir: string, matcher: ReturnType<typeof ignore>}>} */
389
+ const gitIgnoreSources = [];
390
+ /** @type {Array<{file: string, dir: string, matcher: RegExp, line: number}>} */
391
+ const hgIgnoreRules = [];
392
+ const parsedHgFiles = new Set();
393
+ const seenProtectionFiles = new Set();
394
+
395
+ /** @param {string} file */
396
+ function readProtectionFile(file) {
397
+ const relative = slash(path.relative(resolvedRoot, file)) || '.';
398
+ try {
399
+ const override = normalizedOverrides.get(path.resolve(file));
400
+ if (override === null) throw new Error('is deleted by the staged plan');
401
+ const contents =
402
+ override === undefined ? fs.readFileSync(file, 'utf-8') : override;
403
+ if (contents.includes('\0')) throw new Error('contains a NUL byte');
404
+ return {contents, relative};
405
+ } catch (error) {
406
+ if (error instanceof ProtectionSourceError) throw error;
407
+ throw new ProtectionSourceError(
408
+ relative,
409
+ error instanceof Error ? error.message : String(error),
410
+ );
411
+ }
412
+ }
413
+
414
+ /** @param {string} file @param {string} baseDir */
415
+ function parseHgIgnore(file, baseDir) {
416
+ const absolute = path.resolve(file);
417
+ if (parsedHgFiles.has(absolute)) return;
418
+ const within = relativeWithin(absolute, resolvedRoot);
419
+ if (within == null) {
420
+ throw new ProtectionSourceError(
421
+ slash(path.relative(resolvedRoot, absolute)),
422
+ 'included source is outside the operation root',
423
+ );
424
+ }
425
+ const virtualSource =
426
+ normalizedOverrides.has(absolute) &&
427
+ normalizedOverrides.get(absolute) !== null;
428
+ if (!virtualSource) {
429
+ let realSource;
430
+ try {
431
+ realSource = fs.realpathSync(absolute);
432
+ } catch (error) {
433
+ throw new ProtectionSourceError(
434
+ within,
435
+ error instanceof Error ? error.message : String(error),
436
+ );
437
+ }
438
+ if (realSource !== path.resolve(realRoot, within)) {
439
+ throw new ProtectionSourceError(
440
+ within,
441
+ 'included source is reached through a symbolic link',
442
+ );
443
+ }
444
+ } else {
445
+ let ancestor = path.dirname(absolute);
446
+ while (!fs.existsSync(ancestor) && ancestor !== resolvedRoot) {
447
+ ancestor = path.dirname(ancestor);
448
+ }
449
+ const ancestorWithin = relativeWithin(ancestor, resolvedRoot);
450
+ if (
451
+ ancestorWithin == null ||
452
+ fs.realpathSync(ancestor) !== path.resolve(realRoot, ancestorWithin)
453
+ ) {
454
+ throw new ProtectionSourceError(
455
+ within,
456
+ 'staged source is beneath a symbolic link',
457
+ );
458
+ }
459
+ }
460
+ parsedHgFiles.add(absolute);
461
+ const {contents, relative} = readProtectionFile(absolute);
462
+ let syntax = 'regexp';
463
+ const lines = contents.split(/\r?\n/);
464
+ for (let index = 0; index < lines.length; index++) {
465
+ const raw = lines[index].trim();
466
+ if (!raw || raw.startsWith('#')) continue;
467
+ if (raw.startsWith('syntax:')) {
468
+ syntax = raw.slice('syntax:'.length).trim();
469
+ if (syntax !== 'glob' && syntax !== 'regexp') {
470
+ throw new ProtectionSourceError(
471
+ relative,
472
+ `unknown syntax on line ${index + 1}`,
473
+ );
474
+ }
475
+ continue;
476
+ }
477
+ if (raw.startsWith('include:') || raw.startsWith('subinclude:')) {
478
+ const sub = raw.startsWith('subinclude:');
479
+ const includePath = raw.slice(raw.indexOf(':') + 1).trim();
480
+ if (!includePath)
481
+ throw new ProtectionSourceError(
482
+ relative,
483
+ `empty include on line ${index + 1}`,
484
+ );
485
+ const included = path.resolve(path.dirname(absolute), includePath);
486
+ parseHgIgnore(included, sub ? path.dirname(included) : baseDir);
487
+ continue;
488
+ }
489
+ let kind = syntax;
490
+ let pattern = raw;
491
+ if (raw.startsWith('glob:')) {
492
+ kind = 'glob';
493
+ pattern = raw.slice(5);
494
+ } else if (raw.startsWith('re:')) {
495
+ kind = 'regexp';
496
+ pattern = raw.slice(3);
497
+ }
498
+ try {
499
+ const matcher =
500
+ kind === 'glob'
501
+ ? globRegex(pattern.replace(/^\//, ''), {
502
+ basename: !pattern.includes('/'),
503
+ })
504
+ : new RegExp(pattern);
505
+ hgIgnoreRules.push({
506
+ file: relative,
507
+ dir: baseDir,
508
+ matcher,
509
+ line: index + 1,
510
+ });
511
+ } catch (error) {
512
+ throw new ProtectionSourceError(
513
+ relative,
514
+ `invalid ${kind} pattern on line ${index + 1}: ${error instanceof Error ? error.message : String(error)}`,
515
+ );
516
+ }
517
+ }
518
+ }
519
+
520
+ /** @param {string} dir */
521
+ function scan(dir) {
522
+ let entries;
523
+ try {
524
+ entries = fs.readdirSync(dir, {withFileTypes: true});
525
+ } catch (error) {
526
+ throw new ProtectionSourceError(
527
+ slash(path.relative(resolvedRoot, dir)) || '.',
528
+ error instanceof Error ? error.message : String(error),
529
+ );
530
+ }
531
+ // Parent declarations (and attribute macros) must be parsed before child
532
+ // directories regardless of filesystem enumeration order.
533
+ entries.sort((a, b) => Number(a.isDirectory()) - Number(b.isDirectory()));
534
+ for (const entry of entries) {
535
+ const full = path.join(dir, entry.name);
536
+ if (entry.isSymbolicLink()) continue;
537
+ if (entry.isDirectory()) {
538
+ const relativeParts = slash(path.relative(resolvedRoot, full)).split(
539
+ '/',
540
+ );
541
+ if (
542
+ DEPENDENCY_DIRS.has(entry.name) ||
543
+ VCS_DIRS.has(entry.name) ||
544
+ (relativeParts[0] === '.yarn' &&
545
+ YARN_DEPENDENCY_DIRS.has(relativeParts[1]))
546
+ )
547
+ continue;
548
+ scan(full);
549
+ continue;
550
+ }
551
+ if (!PROTECTION_FILES.has(entry.name)) continue;
552
+ if (entry.name === '.hgignore' && dir !== resolvedRoot) continue;
553
+ seenProtectionFiles.add(path.resolve(full));
554
+ if (normalizedOverrides.get(path.resolve(full)) === null) continue;
555
+ const {contents, relative} = readProtectionFile(full);
556
+ if (entry.name === '.gitattributes') {
557
+ rawAttributeSources.push({file: relative, dir, contents});
558
+ } else if (entry.name === '.gitignore') {
559
+ try {
560
+ gitIgnoreSources.push({
561
+ file: relative,
562
+ dir,
563
+ matcher: ignore({ignorecase: false}).add(contents),
564
+ });
565
+ } catch (error) {
566
+ throw new ProtectionSourceError(
567
+ relative,
568
+ error instanceof Error ? error.message : String(error),
569
+ );
570
+ }
571
+ } else if (dir === resolvedRoot) {
572
+ // Mercurial reads only the repository-root .hgignore directly. Nested
573
+ // rule files participate through include:/subinclude: directives.
574
+ parseHgIgnore(full, dir);
575
+ }
576
+ }
577
+ }
578
+
579
+ scan(resolvedRoot);
580
+ for (const [absolute, contents] of normalizedOverrides) {
581
+ if (contents === null || seenProtectionFiles.has(absolute)) continue;
582
+ const name = path.basename(absolute);
583
+ if (!PROTECTION_FILES.has(name)) continue;
584
+ const relative = relativeWithin(absolute, resolvedRoot);
585
+ if (relative == null) continue;
586
+ const dir = path.dirname(absolute);
587
+ if (name === '.hgignore' && dir !== resolvedRoot) continue;
588
+ if (contents.includes('\0')) {
589
+ throw new ProtectionSourceError(relative, 'contains a NUL byte');
590
+ }
591
+ if (name === '.gitattributes') {
592
+ rawAttributeSources.push({file: relative, dir, contents});
593
+ } else if (name === '.gitignore') {
594
+ try {
595
+ gitIgnoreSources.push({
596
+ file: relative,
597
+ dir,
598
+ matcher: ignore({ignorecase: false}).add(contents),
599
+ });
600
+ } catch (error) {
601
+ throw new ProtectionSourceError(
602
+ relative,
603
+ error instanceof Error ? error.message : String(error),
604
+ );
605
+ }
606
+ } else {
607
+ parseHgIgnore(absolute, dir);
608
+ }
609
+ }
610
+ rawAttributeSources.sort(
611
+ (a, b) => a.dir.length - b.dir.length || a.file.localeCompare(b.file),
612
+ );
613
+ for (const source of rawAttributeSources) {
614
+ attributeSources.push({
615
+ file: source.file,
616
+ dir: source.dir,
617
+ rules: parseAttributes(
618
+ source.file,
619
+ source.contents,
620
+ attributeMacros,
621
+ source.dir === resolvedRoot,
622
+ ),
623
+ });
624
+ }
625
+ attributeSources.sort(
626
+ (a, b) => a.dir.length - b.dir.length || a.file.localeCompare(b.file),
627
+ );
628
+ gitIgnoreSources.sort(
629
+ (a, b) => a.dir.length - b.dir.length || a.file.localeCompare(b.file),
630
+ );
631
+
632
+ /** @param {string} target @param {number} end @param {boolean} directory */
633
+ function gitIgnoredBy(
634
+ target,
635
+ end = gitIgnoreSources.length,
636
+ directory = false,
637
+ ) {
638
+ let state = null;
639
+ for (let index = 0; index < end; index++) {
640
+ const source = gitIgnoreSources[index];
641
+ let relative = relativeWithin(target, source.dir);
642
+ if (relative == null || relative === '') continue;
643
+ if (directory) relative += '/';
644
+ // Git never reads a nested ignore file whose parent was excluded by an
645
+ // earlier source. This preserves the "cannot re-include beneath an
646
+ // excluded parent" rule while still allowing lower-file precedence.
647
+ const sourceIndex = gitIgnoreSources.indexOf(source);
648
+ if (
649
+ source.dir !== resolvedRoot &&
650
+ gitIgnoredBy(source.dir, sourceIndex, true)?.ignored
651
+ ) {
652
+ continue;
653
+ }
654
+ const tested = source.matcher.test(relative);
655
+ if (tested.ignored) state = {ignored: true, source, relative};
656
+ else if (tested.unignored) state = {ignored: false, source, relative};
657
+ }
658
+ return state;
659
+ }
660
+
661
+ /** @param {string} filePath @returns {FileProtection[]} */
662
+ function classify(filePath) {
663
+ const absolute = path.resolve(filePath);
664
+ const relative = relativeWithin(absolute, resolvedRoot);
665
+ if (relative == null) {
666
+ return [
667
+ {
668
+ file: slash(path.relative(resolvedRoot, absolute)),
669
+ reason: 'outside-root',
670
+ declaration: 'outside the operation root',
671
+ },
672
+ ];
673
+ }
674
+ const parts = relative.split('/').filter(Boolean);
675
+ if (
676
+ (parts.length === 1 &&
677
+ /^\.pnp\.(?:cjs|loader\.mjs|data\.json)$/.test(parts[0])) ||
678
+ (parts[0] === '.yarn' &&
679
+ (YARN_DEPENDENCY_DIRS.has(parts[1]) ||
680
+ (parts.length === 2 && YARN_DEPENDENCY_FILES.has(parts[1]))))
681
+ ) {
682
+ return [
683
+ {
684
+ file: relative,
685
+ reason: 'dependency',
686
+ declaration: `installed dependency metadata ${parts.slice(0, 2).join('/')}`,
687
+ },
688
+ ];
689
+ }
690
+ for (const part of parts) {
691
+ if (DEPENDENCY_DIRS.has(part))
692
+ return [
693
+ {
694
+ file: relative,
695
+ reason: 'dependency',
696
+ declaration: `installed dependency directory ${part}`,
697
+ },
698
+ ];
699
+ if (VCS_DIRS.has(part))
700
+ return [
701
+ {
702
+ file: relative,
703
+ reason: 'vcs',
704
+ declaration: `version-control metadata directory ${part}`,
705
+ },
706
+ ];
707
+ }
708
+ let current = resolvedRoot;
709
+ for (const part of parts) {
710
+ current = path.join(current, part);
711
+ try {
712
+ if (fs.lstatSync(current).isSymbolicLink()) {
713
+ return [
714
+ {
715
+ file: relative,
716
+ reason: 'symlink',
717
+ declaration: `symbolic link at ${slash(path.relative(resolvedRoot, current))}`,
718
+ },
719
+ ];
720
+ }
721
+ } catch (error) {
722
+ if (
723
+ current === absolute &&
724
+ /** @type {any} */ (error).code === 'ENOENT'
725
+ )
726
+ break;
727
+ throw new ProtectionSourceError(
728
+ relative,
729
+ error instanceof Error ? error.message : String(error),
730
+ );
731
+ }
732
+ }
733
+
734
+ /** @type {FileProtection[]} */
735
+ const protections = [];
736
+ const ignored = gitIgnoredBy(absolute);
737
+ if (ignored?.ignored) {
738
+ protections.push({
739
+ file: relative,
740
+ reason: 'ignored',
741
+ declaration: `excluded by ${ignored.source.file}`,
742
+ source: ignored.source.file,
743
+ });
744
+ }
745
+ for (const rule of hgIgnoreRules) {
746
+ const candidate = relativeWithin(absolute, rule.dir);
747
+ if (candidate != null && rule.matcher.test(candidate)) {
748
+ protections.push({
749
+ file: relative,
750
+ reason: 'ignored',
751
+ declaration: `excluded by ${rule.file}:${rule.line}`,
752
+ source: rule.file,
753
+ line: rule.line,
754
+ });
755
+ }
756
+ }
757
+
758
+ /** @type {Map<string, {value: boolean|null, source: string, line: number}>} */
759
+ const attributes = new Map();
760
+ for (const source of attributeSources) {
761
+ const candidate = relativeWithin(absolute, source.dir);
762
+ if (candidate == null || candidate === '') continue;
763
+ for (const rule of source.rules) {
764
+ if (!rule.pattern.test(candidate)) continue;
765
+ for (const [name, value] of rule.attributes) {
766
+ attributes.set(name, {value, source: source.file, line: rule.line});
767
+ }
768
+ }
769
+ }
770
+ for (const [name, reason] of [
771
+ ['linguist-generated', 'generated'],
772
+ ['linguist-vendored', 'vendored'],
773
+ ]) {
774
+ const attribute = attributes.get(name);
775
+ if (attribute?.value === true) {
776
+ protections.push({
777
+ file: relative,
778
+ reason: /** @type {'generated'|'vendored'} */ (reason),
779
+ declaration: `${name} in ${attribute.source}:${attribute.line}`,
780
+ source: attribute.source,
781
+ line: attribute.line,
782
+ });
783
+ }
784
+ }
785
+
786
+ const hasContentOverride = normalizedOverrides.has(absolute);
787
+ const contentOverride = normalizedOverrides.get(absolute);
788
+ if (
789
+ contentOverride !== null &&
790
+ (hasContentOverride ||
791
+ (fs.existsSync(absolute) && fs.statSync(absolute).isFile()))
792
+ ) {
793
+ let contents;
794
+ try {
795
+ contents = hasContentOverride
796
+ ? /** @type {string} */ (contentOverride)
797
+ : fs.readFileSync(absolute, 'utf-8');
798
+ } catch (error) {
799
+ throw new ProtectionSourceError(
800
+ relative,
801
+ error instanceof Error ? error.message : String(error),
802
+ );
803
+ }
804
+ const marker = generatedMarker(contents, absolute);
805
+ const command = generatedCommand(contents);
806
+ if (marker) {
807
+ protections.push({
808
+ file: relative,
809
+ reason: 'generated',
810
+ declaration: marker,
811
+ command,
812
+ });
813
+ }
814
+ if (command) {
815
+ for (const item of protections) {
816
+ if (item.reason === 'generated' && !item.command)
817
+ item.command = command;
818
+ }
819
+ }
820
+ }
821
+ return protections;
822
+ }
823
+
824
+ return Object.freeze({root: resolvedRoot, classify});
825
+ }