@zigrivers/scaffold 3.9.2 → 3.10.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 (214) hide show
  1. package/README.md +39 -8
  2. package/dist/cli/commands/adopt.cli-flags.test.d.ts +2 -0
  3. package/dist/cli/commands/adopt.cli-flags.test.d.ts.map +1 -0
  4. package/dist/cli/commands/adopt.cli-flags.test.js +165 -0
  5. package/dist/cli/commands/adopt.cli-flags.test.js.map +1 -0
  6. package/dist/cli/commands/adopt.config-resolution.test.d.ts +2 -0
  7. package/dist/cli/commands/adopt.config-resolution.test.d.ts.map +1 -0
  8. package/dist/cli/commands/adopt.config-resolution.test.js +217 -0
  9. package/dist/cli/commands/adopt.config-resolution.test.js.map +1 -0
  10. package/dist/cli/commands/adopt.config-write-integration.test.d.ts +2 -0
  11. package/dist/cli/commands/adopt.config-write-integration.test.d.ts.map +1 -0
  12. package/dist/cli/commands/adopt.config-write-integration.test.js +138 -0
  13. package/dist/cli/commands/adopt.config-write-integration.test.js.map +1 -0
  14. package/dist/cli/commands/adopt.d.ts +4 -0
  15. package/dist/cli/commands/adopt.d.ts.map +1 -1
  16. package/dist/cli/commands/adopt.js +432 -61
  17. package/dist/cli/commands/adopt.js.map +1 -1
  18. package/dist/cli/commands/adopt.performance.test.d.ts +2 -0
  19. package/dist/cli/commands/adopt.performance.test.d.ts.map +1 -0
  20. package/dist/cli/commands/adopt.performance.test.js +30 -0
  21. package/dist/cli/commands/adopt.performance.test.js.map +1 -0
  22. package/dist/cli/commands/adopt.result-shape.test.d.ts +2 -0
  23. package/dist/cli/commands/adopt.result-shape.test.d.ts.map +1 -0
  24. package/dist/cli/commands/adopt.result-shape.test.js +166 -0
  25. package/dist/cli/commands/adopt.result-shape.test.js.map +1 -0
  26. package/dist/cli/commands/adopt.serialization.test.d.ts +2 -0
  27. package/dist/cli/commands/adopt.serialization.test.d.ts.map +1 -0
  28. package/dist/cli/commands/adopt.serialization.test.js +228 -0
  29. package/dist/cli/commands/adopt.serialization.test.js.map +1 -0
  30. package/dist/cli/commands/adopt.test.js +26 -15
  31. package/dist/cli/commands/adopt.test.js.map +1 -1
  32. package/dist/cli/commands/adopt.windows-crlf.test.d.ts +2 -0
  33. package/dist/cli/commands/adopt.windows-crlf.test.d.ts.map +1 -0
  34. package/dist/cli/commands/adopt.windows-crlf.test.js +102 -0
  35. package/dist/cli/commands/adopt.windows-crlf.test.js.map +1 -0
  36. package/dist/cli/commands/init.d.ts.map +1 -1
  37. package/dist/cli/commands/init.js +4 -157
  38. package/dist/cli/commands/init.js.map +1 -1
  39. package/dist/cli/init-flag-families.d.ts +86 -0
  40. package/dist/cli/init-flag-families.d.ts.map +1 -0
  41. package/dist/cli/init-flag-families.js +418 -0
  42. package/dist/cli/init-flag-families.js.map +1 -0
  43. package/dist/cli/init-flag-families.test.d.ts +2 -0
  44. package/dist/cli/init-flag-families.test.d.ts.map +1 -0
  45. package/dist/cli/init-flag-families.test.js +268 -0
  46. package/dist/cli/init-flag-families.test.js.map +1 -0
  47. package/dist/config/schema.d.ts +6 -6
  48. package/dist/config/schema.js +2 -2
  49. package/dist/config/schema.js.map +1 -1
  50. package/dist/config/schema.test.js +10 -4
  51. package/dist/config/schema.test.js.map +1 -1
  52. package/dist/e2e/adopt-multi-type.test.d.ts +2 -0
  53. package/dist/e2e/adopt-multi-type.test.d.ts.map +1 -0
  54. package/dist/e2e/adopt-multi-type.test.js +35 -0
  55. package/dist/e2e/adopt-multi-type.test.js.map +1 -0
  56. package/dist/project/__frozen-schemas__/schema-v3.9.2.d.ts +2891 -0
  57. package/dist/project/__frozen-schemas__/schema-v3.9.2.d.ts.map +1 -0
  58. package/dist/project/__frozen-schemas__/schema-v3.9.2.js +177 -0
  59. package/dist/project/__frozen-schemas__/schema-v3.9.2.js.map +1 -0
  60. package/dist/project/adopt.d.ts +38 -2
  61. package/dist/project/adopt.d.ts.map +1 -1
  62. package/dist/project/adopt.error-messages.test.d.ts +2 -0
  63. package/dist/project/adopt.error-messages.test.d.ts.map +1 -0
  64. package/dist/project/adopt.error-messages.test.js +112 -0
  65. package/dist/project/adopt.error-messages.test.js.map +1 -0
  66. package/dist/project/adopt.forward-compat.test.d.ts +2 -0
  67. package/dist/project/adopt.forward-compat.test.d.ts.map +1 -0
  68. package/dist/project/adopt.forward-compat.test.js +71 -0
  69. package/dist/project/adopt.forward-compat.test.js.map +1 -0
  70. package/dist/project/adopt.js +218 -27
  71. package/dist/project/adopt.js.map +1 -1
  72. package/dist/project/adopt.merge.test.d.ts +2 -0
  73. package/dist/project/adopt.merge.test.d.ts.map +1 -0
  74. package/dist/project/adopt.merge.test.js +62 -0
  75. package/dist/project/adopt.merge.test.js.map +1 -0
  76. package/dist/project/adopt.re-adoption.test.d.ts +2 -0
  77. package/dist/project/adopt.re-adoption.test.d.ts.map +1 -0
  78. package/dist/project/adopt.re-adoption.test.js +222 -0
  79. package/dist/project/adopt.re-adoption.test.js.map +1 -0
  80. package/dist/project/adopt.test.js +72 -27
  81. package/dist/project/adopt.test.js.map +1 -1
  82. package/dist/project/detectors/backend.d.ts +4 -0
  83. package/dist/project/detectors/backend.d.ts.map +1 -0
  84. package/dist/project/detectors/backend.js +199 -0
  85. package/dist/project/detectors/backend.js.map +1 -0
  86. package/dist/project/detectors/backend.test.d.ts +2 -0
  87. package/dist/project/detectors/backend.test.d.ts.map +1 -0
  88. package/dist/project/detectors/backend.test.js +64 -0
  89. package/dist/project/detectors/backend.test.js.map +1 -0
  90. package/dist/project/detectors/browser-extension.d.ts +4 -0
  91. package/dist/project/detectors/browser-extension.d.ts.map +1 -0
  92. package/dist/project/detectors/browser-extension.js +61 -0
  93. package/dist/project/detectors/browser-extension.js.map +1 -0
  94. package/dist/project/detectors/browser-extension.test.d.ts +2 -0
  95. package/dist/project/detectors/browser-extension.test.d.ts.map +1 -0
  96. package/dist/project/detectors/browser-extension.test.js +64 -0
  97. package/dist/project/detectors/browser-extension.test.js.map +1 -0
  98. package/dist/project/detectors/cli.d.ts +4 -0
  99. package/dist/project/detectors/cli.d.ts.map +1 -0
  100. package/dist/project/detectors/cli.js +68 -0
  101. package/dist/project/detectors/cli.js.map +1 -0
  102. package/dist/project/detectors/cli.test.d.ts +2 -0
  103. package/dist/project/detectors/cli.test.d.ts.map +1 -0
  104. package/dist/project/detectors/cli.test.js +70 -0
  105. package/dist/project/detectors/cli.test.js.map +1 -0
  106. package/dist/project/detectors/context.d.ts +105 -0
  107. package/dist/project/detectors/context.d.ts.map +1 -0
  108. package/dist/project/detectors/context.js +652 -0
  109. package/dist/project/detectors/context.js.map +1 -0
  110. package/dist/project/detectors/context.test.d.ts +2 -0
  111. package/dist/project/detectors/context.test.d.ts.map +1 -0
  112. package/dist/project/detectors/context.test.js +240 -0
  113. package/dist/project/detectors/context.test.js.map +1 -0
  114. package/dist/project/detectors/data-pipeline.d.ts +4 -0
  115. package/dist/project/detectors/data-pipeline.d.ts.map +1 -0
  116. package/dist/project/detectors/data-pipeline.js +97 -0
  117. package/dist/project/detectors/data-pipeline.js.map +1 -0
  118. package/dist/project/detectors/data-pipeline.test.d.ts +2 -0
  119. package/dist/project/detectors/data-pipeline.test.d.ts.map +1 -0
  120. package/dist/project/detectors/data-pipeline.test.js +67 -0
  121. package/dist/project/detectors/data-pipeline.test.js.map +1 -0
  122. package/dist/project/detectors/disambiguate.d.ts +12 -0
  123. package/dist/project/detectors/disambiguate.d.ts.map +1 -0
  124. package/dist/project/detectors/disambiguate.js +87 -0
  125. package/dist/project/detectors/disambiguate.js.map +1 -0
  126. package/dist/project/detectors/disambiguate.test.d.ts +2 -0
  127. package/dist/project/detectors/disambiguate.test.d.ts.map +1 -0
  128. package/dist/project/detectors/disambiguate.test.js +114 -0
  129. package/dist/project/detectors/disambiguate.test.js.map +1 -0
  130. package/dist/project/detectors/file-text-match.d.ts +3 -0
  131. package/dist/project/detectors/file-text-match.d.ts.map +1 -0
  132. package/dist/project/detectors/file-text-match.js +60 -0
  133. package/dist/project/detectors/file-text-match.js.map +1 -0
  134. package/dist/project/detectors/file-text-match.test.d.ts +2 -0
  135. package/dist/project/detectors/file-text-match.test.d.ts.map +1 -0
  136. package/dist/project/detectors/file-text-match.test.js +81 -0
  137. package/dist/project/detectors/file-text-match.test.js.map +1 -0
  138. package/dist/project/detectors/game.d.ts +18 -0
  139. package/dist/project/detectors/game.d.ts.map +1 -0
  140. package/dist/project/detectors/game.js +83 -0
  141. package/dist/project/detectors/game.js.map +1 -0
  142. package/dist/project/detectors/game.test.d.ts +2 -0
  143. package/dist/project/detectors/game.test.d.ts.map +1 -0
  144. package/dist/project/detectors/game.test.js +87 -0
  145. package/dist/project/detectors/game.test.js.map +1 -0
  146. package/dist/project/detectors/index.d.ts +5 -0
  147. package/dist/project/detectors/index.d.ts.map +1 -0
  148. package/dist/project/detectors/index.js +29 -0
  149. package/dist/project/detectors/index.js.map +1 -0
  150. package/dist/project/detectors/library.d.ts +4 -0
  151. package/dist/project/detectors/library.d.ts.map +1 -0
  152. package/dist/project/detectors/library.js +122 -0
  153. package/dist/project/detectors/library.js.map +1 -0
  154. package/dist/project/detectors/library.test.d.ts +2 -0
  155. package/dist/project/detectors/library.test.d.ts.map +1 -0
  156. package/dist/project/detectors/library.test.js +95 -0
  157. package/dist/project/detectors/library.test.js.map +1 -0
  158. package/dist/project/detectors/ml.d.ts +4 -0
  159. package/dist/project/detectors/ml.d.ts.map +1 -0
  160. package/dist/project/detectors/ml.js +112 -0
  161. package/dist/project/detectors/ml.js.map +1 -0
  162. package/dist/project/detectors/ml.test.d.ts +2 -0
  163. package/dist/project/detectors/ml.test.d.ts.map +1 -0
  164. package/dist/project/detectors/ml.test.js +75 -0
  165. package/dist/project/detectors/ml.test.js.map +1 -0
  166. package/dist/project/detectors/mobile-app.d.ts +4 -0
  167. package/dist/project/detectors/mobile-app.d.ts.map +1 -0
  168. package/dist/project/detectors/mobile-app.js +58 -0
  169. package/dist/project/detectors/mobile-app.js.map +1 -0
  170. package/dist/project/detectors/mobile-app.test.d.ts +2 -0
  171. package/dist/project/detectors/mobile-app.test.d.ts.map +1 -0
  172. package/dist/project/detectors/mobile-app.test.js +57 -0
  173. package/dist/project/detectors/mobile-app.test.js.map +1 -0
  174. package/dist/project/detectors/required-fields.d.ts +4 -0
  175. package/dist/project/detectors/required-fields.d.ts.map +1 -0
  176. package/dist/project/detectors/required-fields.js +13 -0
  177. package/dist/project/detectors/required-fields.js.map +1 -0
  178. package/dist/project/detectors/required-fields.test.d.ts +2 -0
  179. package/dist/project/detectors/required-fields.test.d.ts.map +1 -0
  180. package/dist/project/detectors/required-fields.test.js +24 -0
  181. package/dist/project/detectors/required-fields.test.js.map +1 -0
  182. package/dist/project/detectors/resolve-detection.d.ts +18 -0
  183. package/dist/project/detectors/resolve-detection.d.ts.map +1 -0
  184. package/dist/project/detectors/resolve-detection.js +184 -0
  185. package/dist/project/detectors/resolve-detection.js.map +1 -0
  186. package/dist/project/detectors/resolve-detection.test.d.ts +2 -0
  187. package/dist/project/detectors/resolve-detection.test.d.ts.map +1 -0
  188. package/dist/project/detectors/resolve-detection.test.js +158 -0
  189. package/dist/project/detectors/resolve-detection.test.js.map +1 -0
  190. package/dist/project/detectors/types.d.ts +56 -0
  191. package/dist/project/detectors/types.d.ts.map +1 -0
  192. package/dist/project/detectors/types.js +9 -0
  193. package/dist/project/detectors/types.js.map +1 -0
  194. package/dist/project/detectors/web-app.d.ts +15 -0
  195. package/dist/project/detectors/web-app.d.ts.map +1 -0
  196. package/dist/project/detectors/web-app.js +228 -0
  197. package/dist/project/detectors/web-app.js.map +1 -0
  198. package/dist/project/detectors/web-app.test.d.ts +2 -0
  199. package/dist/project/detectors/web-app.test.d.ts.map +1 -0
  200. package/dist/project/detectors/web-app.test.js +95 -0
  201. package/dist/project/detectors/web-app.test.js.map +1 -0
  202. package/dist/types/config.d.ts +33 -0
  203. package/dist/types/config.d.ts.map +1 -1
  204. package/dist/types/enums.d.ts +2 -1
  205. package/dist/types/enums.d.ts.map +1 -1
  206. package/dist/types/enums.js +1 -0
  207. package/dist/types/enums.js.map +1 -1
  208. package/dist/utils/errors.d.ts +7 -0
  209. package/dist/utils/errors.d.ts.map +1 -1
  210. package/dist/utils/errors.js +38 -0
  211. package/dist/utils/errors.js.map +1 -1
  212. package/dist/utils/errors.test.js +42 -1
  213. package/dist/utils/errors.test.js.map +1 -1
  214. package/package.json +6 -2
@@ -0,0 +1,652 @@
1
+ // src/project/detectors/context.ts
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { parse as parseTOML } from 'smol-toml';
5
+ import { z } from 'zod';
6
+ // PEP 503 name normalization
7
+ function normalizePep503(name) {
8
+ return name.toLowerCase().replace(/[-_.]+/g, '-');
9
+ }
10
+ // Extract bare package name from a PEP 508 dep spec
11
+ function extractPyName(spec) {
12
+ // Strip environment marker
13
+ let s = spec.split(';')[0];
14
+ // Strip URL fragment
15
+ s = s.split('@')[0];
16
+ // Strip version specs — include `(` for PEP 508 parenthesis form,
17
+ // e.g. `Django (>=2.0)` which should normalize to `django`.
18
+ s = s.replace(/[(=<>!~].*$/, '');
19
+ // Strip extras
20
+ s = s.replace(/\[.*?\]/, '');
21
+ return normalizePep503(s.trim());
22
+ }
23
+ // Module-private dep matchers — shared by real and fake contexts
24
+ const NPM_ALL_SCOPES = ['deps', 'dev', 'peer', 'optional'];
25
+ function npmBucket(pkg, s) {
26
+ return s === 'deps' ? pkg.dependencies
27
+ : s === 'dev' ? pkg.devDependencies
28
+ : s === 'peer' ? pkg.peerDependencies
29
+ : pkg.optionalDependencies;
30
+ }
31
+ function resolveNpmScopes(scope) {
32
+ if (scope === 'all')
33
+ return NPM_ALL_SCOPES;
34
+ if (scope === 'deps' || scope === 'dev' || scope === 'peer' || scope === 'optional')
35
+ return [scope];
36
+ return NPM_ALL_SCOPES; // defensive fallback — matches old real-context behavior
37
+ }
38
+ function matchNpmDep(pkg, name, scope) {
39
+ if (!pkg)
40
+ return false;
41
+ for (const s of resolveNpmScopes(scope)) {
42
+ const bucket = npmBucket(pkg, s);
43
+ if (bucket && name in bucket)
44
+ return true;
45
+ }
46
+ return false;
47
+ }
48
+ function matchPyDep(py, name) {
49
+ if (!py)
50
+ return false;
51
+ const normalized = normalizePep503(name);
52
+ const pep621 = py.project?.dependencies ?? [];
53
+ for (const spec of pep621) {
54
+ if (extractPyName(spec) === normalized)
55
+ return true;
56
+ }
57
+ const poetryDeps = py.tool?.poetry?.dependencies;
58
+ if (poetryDeps) {
59
+ for (const key of Object.keys(poetryDeps)) {
60
+ if (key === 'python')
61
+ continue;
62
+ if (normalizePep503(key) === normalized)
63
+ return true;
64
+ }
65
+ }
66
+ const poetryDev = py.tool?.poetry?.['dev-dependencies'];
67
+ if (poetryDev) {
68
+ for (const key of Object.keys(poetryDev)) {
69
+ if (normalizePep503(key) === normalized)
70
+ return true;
71
+ }
72
+ }
73
+ const groups = py.tool?.poetry?.group ?? {};
74
+ for (const group of Object.values(groups)) {
75
+ if (group.dependencies) {
76
+ for (const key of Object.keys(group.dependencies)) {
77
+ if (normalizePep503(key) === normalized)
78
+ return true;
79
+ }
80
+ }
81
+ }
82
+ return false;
83
+ }
84
+ function matchCargoDep(cargo, name) {
85
+ if (!cargo)
86
+ return false;
87
+ if (cargo.dependencies && name in cargo.dependencies)
88
+ return true;
89
+ if (cargo['dev-dependencies'] && name in cargo['dev-dependencies'])
90
+ return true;
91
+ return false;
92
+ }
93
+ function matchGoDep(go, name) {
94
+ if (!go)
95
+ return false;
96
+ for (const req of go.requires ?? []) {
97
+ if (req.indirect)
98
+ continue;
99
+ if (req.path === name)
100
+ return true;
101
+ if (req.path.startsWith(`${name}/`))
102
+ return true;
103
+ }
104
+ return false;
105
+ }
106
+ // go.mod parser (handles multi-line require blocks + // indirect)
107
+ function parseGoMod(content) {
108
+ const result = { requires: [] };
109
+ const lines = content.split('\n');
110
+ let inRequireBlock = false;
111
+ for (const rawLine of lines) {
112
+ const line = rawLine.replace(/\/\/.*$/, '').trim();
113
+ if (!line)
114
+ continue;
115
+ if (line.startsWith('module ')) {
116
+ result.module = line.slice(7).trim();
117
+ continue;
118
+ }
119
+ if (line.startsWith('go ')) {
120
+ result.goVersion = line.slice(3).trim();
121
+ continue;
122
+ }
123
+ if (line.startsWith('require (')) {
124
+ inRequireBlock = true;
125
+ continue;
126
+ }
127
+ if (inRequireBlock && line === ')') {
128
+ inRequireBlock = false;
129
+ continue;
130
+ }
131
+ if (inRequireBlock || line.startsWith('require ')) {
132
+ const body = line.startsWith('require ') ? line.slice(8) : line;
133
+ const indirect = /\/\/\s*indirect\b/.test(rawLine);
134
+ const parts = body.trim().split(/\s+/);
135
+ if (parts.length >= 2) {
136
+ result.requires.push({ path: parts[0], version: parts[1], indirect });
137
+ }
138
+ }
139
+ // replace/exclude directives parsed-and-discarded per spec
140
+ }
141
+ return result;
142
+ }
143
+ export function createSignalContext(projectRoot) {
144
+ const warnings = [];
145
+ const fileCache = new Map();
146
+ const dirCache = new Map();
147
+ const textCache = new Map();
148
+ const parseCache = {};
149
+ const status = {
150
+ npm: 'missing', py: 'missing', cargo: 'missing', go: 'missing',
151
+ };
152
+ // Eager root readdir + manifest probes
153
+ let rootEntriesCache;
154
+ try {
155
+ const entries = fs.readdirSync(projectRoot, { withFileTypes: true });
156
+ rootEntriesCache = entries.map(e => e.name).sort();
157
+ }
158
+ catch (err) {
159
+ warnings.push({
160
+ code: 'ADOPT_FS_INACCESSIBLE',
161
+ message: `Cannot read project root: ${err.message}`,
162
+ context: { path: projectRoot },
163
+ });
164
+ rootEntriesCache = [];
165
+ }
166
+ // Eager-stat probe list
167
+ const PROBE = ['package.json', 'pyproject.toml', 'Cargo.toml', 'go.mod',
168
+ 'app.json', 'project.godot', 'manifest.json'];
169
+ for (const p of PROBE) {
170
+ try {
171
+ const stat = fs.statSync(path.join(projectRoot, p), { throwIfNoEntry: false });
172
+ fileCache.set(p, !!stat && stat.isFile());
173
+ }
174
+ catch {
175
+ fileCache.set(p, false);
176
+ }
177
+ }
178
+ function hasFile(relPath) {
179
+ const cached = fileCache.get(relPath);
180
+ if (cached !== undefined)
181
+ return cached;
182
+ try {
183
+ const stat = fs.statSync(path.join(projectRoot, relPath), { throwIfNoEntry: false });
184
+ const exists = !!stat && stat.isFile();
185
+ fileCache.set(relPath, exists);
186
+ return exists;
187
+ }
188
+ catch (err) {
189
+ // ENOENT/ENOTDIR are normal "not present" probe results — silent.
190
+ // Only emit a warning on real access failures (EACCES, EIO, etc.).
191
+ const code = err.code;
192
+ if (code !== 'ENOENT' && code !== 'ENOTDIR') {
193
+ warnings.push({
194
+ code: 'ADOPT_FS_INACCESSIBLE',
195
+ message: `Cannot stat file: ${err.message}`,
196
+ context: { path: relPath },
197
+ });
198
+ }
199
+ fileCache.set(relPath, false);
200
+ return false;
201
+ }
202
+ }
203
+ function dirExists(relPath) {
204
+ const cached = dirCache.get(relPath);
205
+ if (cached !== undefined)
206
+ return cached;
207
+ try {
208
+ const stat = fs.statSync(path.join(projectRoot, relPath), { throwIfNoEntry: false });
209
+ const exists = !!stat && stat.isDirectory();
210
+ dirCache.set(relPath, exists);
211
+ return exists;
212
+ }
213
+ catch (err) {
214
+ // ENOENT/ENOTDIR are normal "not present" probe results — silent.
215
+ const code = err.code;
216
+ if (code !== 'ENOENT' && code !== 'ENOTDIR') {
217
+ warnings.push({
218
+ code: 'ADOPT_FS_INACCESSIBLE',
219
+ message: `Cannot stat directory: ${err.message}`,
220
+ context: { path: relPath },
221
+ });
222
+ }
223
+ dirCache.set(relPath, false);
224
+ return false;
225
+ }
226
+ }
227
+ function rootEntries() {
228
+ return rootEntriesCache;
229
+ }
230
+ const listDirCache = new Map();
231
+ function listDir(relPath) {
232
+ const cached = listDirCache.get(relPath);
233
+ if (cached !== undefined)
234
+ return cached;
235
+ try {
236
+ const entries = fs.readdirSync(path.join(projectRoot, relPath), { withFileTypes: true });
237
+ const names = entries.map(e => e.name).sort();
238
+ listDirCache.set(relPath, names);
239
+ return names;
240
+ }
241
+ catch (err) {
242
+ // ENOENT/ENOTDIR are expected for detectors probing optional paths
243
+ // like app/, ios/, android/ — silent. Only warn on real access failures.
244
+ const code = err.code;
245
+ if (code !== 'ENOENT' && code !== 'ENOTDIR') {
246
+ warnings.push({
247
+ code: 'ADOPT_FS_INACCESSIBLE',
248
+ message: `Cannot list directory: ${err.message}`,
249
+ context: { path: relPath },
250
+ });
251
+ }
252
+ const empty = [];
253
+ listDirCache.set(relPath, empty);
254
+ return empty;
255
+ }
256
+ }
257
+ function readFileText(relPath, maxBytes = 262144) {
258
+ // Cache stores FULL content (or undefined for missing/unreadable). Truncated
259
+ // reads are never cached — otherwise a later call with a larger maxBytes
260
+ // would receive the stale truncated value.
261
+ if (textCache.has(relPath))
262
+ return textCache.get(relPath);
263
+ try {
264
+ const full = path.join(projectRoot, relPath);
265
+ const stat = fs.statSync(full, { throwIfNoEntry: false });
266
+ if (!stat || !stat.isFile()) {
267
+ textCache.set(relPath, undefined);
268
+ return undefined;
269
+ }
270
+ if (stat.size > maxBytes) {
271
+ const fd = fs.openSync(full, 'r');
272
+ const buf = Buffer.alloc(maxBytes);
273
+ let bytesRead = 0;
274
+ try {
275
+ bytesRead = fs.readSync(fd, buf, 0, maxBytes, 0);
276
+ }
277
+ finally {
278
+ fs.closeSync(fd);
279
+ }
280
+ warnings.push({
281
+ code: 'ADOPT_FILE_TRUNCATED',
282
+ message: `File truncated to ${maxBytes} bytes`,
283
+ context: { path: relPath, size: stat.size },
284
+ });
285
+ // Trim to bytesRead so a short read doesn't leave trailing null bytes.
286
+ // Do NOT cache — we only have a partial read and a future caller may
287
+ // request a larger slice.
288
+ return buf.toString('utf8', 0, bytesRead);
289
+ }
290
+ const content = fs.readFileSync(full, 'utf8');
291
+ textCache.set(relPath, content);
292
+ return content;
293
+ }
294
+ catch (err) {
295
+ warnings.push({
296
+ code: 'ADOPT_FILE_UNREADABLE',
297
+ message: `Cannot read file: ${err.message}`,
298
+ context: { path: relPath },
299
+ });
300
+ textCache.set(relPath, undefined);
301
+ return undefined;
302
+ }
303
+ }
304
+ function manifestStatus(kind) {
305
+ // Lazily trigger parse so the status reflects reality regardless of call order.
306
+ // Without this, a detector that calls manifestStatus() as a pre-check before
307
+ // calling the parser accessor would see 'missing' even when the file exists.
308
+ if (kind === 'npm' && !('packageJson' in parseCache))
309
+ packageJson();
310
+ else if (kind === 'py' && !('pyprojectToml' in parseCache))
311
+ pyprojectToml();
312
+ else if (kind === 'cargo' && !('cargoToml' in parseCache))
313
+ cargoToml();
314
+ else if (kind === 'go' && !('goMod' in parseCache))
315
+ goMod();
316
+ return status[kind];
317
+ }
318
+ // Zod schemas for manifest slices — runtime validation prevents type errors
319
+ // from malformed manifests (e.g., `dependencies: []` instead of an object)
320
+ const zDepRecord = z.record(z.string(), z.string()).optional();
321
+ const PackageJsonSchema = z.object({
322
+ name: z.string().optional(),
323
+ version: z.string().optional(),
324
+ private: z.boolean().optional(),
325
+ main: z.string().optional(),
326
+ module: z.string().optional(),
327
+ types: z.string().optional(),
328
+ typings: z.string().optional(),
329
+ // browser can be a string OR a map; map values can be string OR false (npm spec
330
+ // allows `{"fs": false}` to mean "exclude this module from browser builds").
331
+ // Use z.unknown() at the value level to be permissive.
332
+ browser: z.union([z.string(), z.record(z.string(), z.unknown())]).optional(),
333
+ exports: z.unknown().optional(),
334
+ bin: z.union([z.string(), z.record(z.string(), z.string())]).optional(),
335
+ type: z.enum(['module', 'commonjs']).optional(),
336
+ engines: z.record(z.string(), z.string()).optional(),
337
+ scripts: z.record(z.string(), z.string()).optional(),
338
+ dependencies: zDepRecord,
339
+ devDependencies: zDepRecord,
340
+ peerDependencies: zDepRecord,
341
+ optionalDependencies: zDepRecord,
342
+ // workspaces: array form OR object form with .passthrough() so extras like
343
+ // `nohoist`, `overrides`, or package-manager-specific keys don't reject.
344
+ workspaces: z.union([
345
+ z.array(z.string()),
346
+ z.object({ packages: z.array(z.string()).optional() }).passthrough(),
347
+ ]).optional(),
348
+ }).passthrough();
349
+ const PyprojectTomlSchema = z.object({
350
+ project: z.object({
351
+ name: z.string().optional(),
352
+ dependencies: z.array(z.string()).optional(),
353
+ 'optional-dependencies': z.record(z.string(), z.array(z.string())).optional(),
354
+ scripts: z.record(z.string(), z.string()).optional(),
355
+ }).passthrough().optional(),
356
+ tool: z.record(z.string(), z.unknown()).optional(),
357
+ 'build-system': z.object({ requires: z.array(z.string()).optional() }).passthrough().optional(),
358
+ }).passthrough();
359
+ const CargoTomlSchema = z.object({
360
+ package: z.object({
361
+ name: z.string().optional(),
362
+ version: z.string().optional(),
363
+ publish: z.union([z.boolean(), z.array(z.string())]).optional(),
364
+ }).passthrough().optional(),
365
+ dependencies: z.record(z.string(), z.unknown()).optional(),
366
+ 'dev-dependencies': z.record(z.string(), z.unknown()).optional(),
367
+ lib: z.record(z.string(), z.unknown()).optional(),
368
+ bin: z.array(z.object({ name: z.string(), path: z.string().optional() }).passthrough()).optional(),
369
+ }).passthrough();
370
+ function packageJson() {
371
+ if ('packageJson' in parseCache)
372
+ return parseCache.packageJson;
373
+ if (!hasFile('package.json')) {
374
+ status.npm = 'missing';
375
+ parseCache.packageJson = undefined;
376
+ return undefined;
377
+ }
378
+ const text = readFileText('package.json');
379
+ if (text === undefined) {
380
+ status.npm = 'unparseable';
381
+ parseCache.packageJson = undefined;
382
+ return undefined;
383
+ }
384
+ try {
385
+ const raw = JSON.parse(text);
386
+ const parsed = PackageJsonSchema.safeParse(raw);
387
+ if (!parsed.success) {
388
+ warnings.push({
389
+ code: 'ADOPT_MANIFEST_UNPARSEABLE',
390
+ message: `package.json schema validation failed: ${parsed.error.errors[0]?.message ?? 'unknown'}`,
391
+ context: { path: 'package.json' },
392
+ });
393
+ status.npm = 'unparseable';
394
+ parseCache.packageJson = undefined;
395
+ return undefined;
396
+ }
397
+ status.npm = 'parsed';
398
+ parseCache.packageJson = parsed.data;
399
+ return parsed.data;
400
+ }
401
+ catch (err) {
402
+ warnings.push({
403
+ code: 'ADOPT_MANIFEST_UNPARSEABLE',
404
+ message: `package.json parse failed: ${err.message}`,
405
+ context: { path: 'package.json' },
406
+ });
407
+ status.npm = 'unparseable';
408
+ parseCache.packageJson = undefined;
409
+ return undefined;
410
+ }
411
+ }
412
+ function pyprojectToml() {
413
+ if ('pyprojectToml' in parseCache)
414
+ return parseCache.pyprojectToml;
415
+ if (!hasFile('pyproject.toml')) {
416
+ status.py = 'missing';
417
+ parseCache.pyprojectToml = undefined;
418
+ return undefined;
419
+ }
420
+ const text = readFileText('pyproject.toml');
421
+ if (text === undefined) {
422
+ status.py = 'unparseable';
423
+ parseCache.pyprojectToml = undefined;
424
+ return undefined;
425
+ }
426
+ try {
427
+ const raw = parseTOML(text);
428
+ const parsed = PyprojectTomlSchema.safeParse(raw);
429
+ if (!parsed.success) {
430
+ warnings.push({
431
+ code: 'ADOPT_MANIFEST_UNPARSEABLE',
432
+ message: `pyproject.toml schema validation failed: ${parsed.error.errors[0]?.message ?? 'unknown'}`,
433
+ context: { path: 'pyproject.toml' },
434
+ });
435
+ status.py = 'unparseable';
436
+ parseCache.pyprojectToml = undefined;
437
+ return undefined;
438
+ }
439
+ status.py = 'parsed';
440
+ parseCache.pyprojectToml = parsed.data;
441
+ return parsed.data;
442
+ }
443
+ catch (err) {
444
+ warnings.push({
445
+ code: 'ADOPT_MANIFEST_UNPARSEABLE',
446
+ message: `pyproject.toml parse failed: ${err.message}`,
447
+ context: { path: 'pyproject.toml' },
448
+ });
449
+ status.py = 'unparseable';
450
+ parseCache.pyprojectToml = undefined;
451
+ return undefined;
452
+ }
453
+ }
454
+ function cargoToml() {
455
+ if ('cargoToml' in parseCache)
456
+ return parseCache.cargoToml;
457
+ if (!hasFile('Cargo.toml')) {
458
+ status.cargo = 'missing';
459
+ parseCache.cargoToml = undefined;
460
+ return undefined;
461
+ }
462
+ const text = readFileText('Cargo.toml');
463
+ if (text === undefined) {
464
+ status.cargo = 'unparseable';
465
+ parseCache.cargoToml = undefined;
466
+ return undefined;
467
+ }
468
+ try {
469
+ const raw = parseTOML(text);
470
+ const parsed = CargoTomlSchema.safeParse(raw);
471
+ if (!parsed.success) {
472
+ warnings.push({
473
+ code: 'ADOPT_MANIFEST_UNPARSEABLE',
474
+ message: `Cargo.toml schema validation failed: ${parsed.error.errors[0]?.message ?? 'unknown'}`,
475
+ context: { path: 'Cargo.toml' },
476
+ });
477
+ status.cargo = 'unparseable';
478
+ parseCache.cargoToml = undefined;
479
+ return undefined;
480
+ }
481
+ status.cargo = 'parsed';
482
+ parseCache.cargoToml = parsed.data;
483
+ return parsed.data;
484
+ }
485
+ catch (err) {
486
+ warnings.push({
487
+ code: 'ADOPT_MANIFEST_UNPARSEABLE',
488
+ message: `Cargo.toml parse failed: ${err.message}`,
489
+ context: { path: 'Cargo.toml' },
490
+ });
491
+ status.cargo = 'unparseable';
492
+ parseCache.cargoToml = undefined;
493
+ return undefined;
494
+ }
495
+ }
496
+ function goMod() {
497
+ if ('goMod' in parseCache)
498
+ return parseCache.goMod;
499
+ if (!hasFile('go.mod')) {
500
+ status.go = 'missing';
501
+ parseCache.goMod = undefined;
502
+ return undefined;
503
+ }
504
+ const text = readFileText('go.mod');
505
+ if (text === undefined) {
506
+ status.go = 'unparseable';
507
+ parseCache.goMod = undefined;
508
+ return undefined;
509
+ }
510
+ try {
511
+ const parsed = parseGoMod(text);
512
+ // Minimal structural validation: a valid go.mod must declare a module.
513
+ // Without this check, completely malformed input like "<<garbage>>" would
514
+ // silently produce an empty GoMod object — every other manifest accessor
515
+ // marks malformed input as 'unparseable'.
516
+ if (!parsed.module) {
517
+ warnings.push({
518
+ code: 'ADOPT_MANIFEST_UNPARSEABLE',
519
+ message: 'go.mod parse failed: missing module directive',
520
+ context: { path: 'go.mod' },
521
+ });
522
+ status.go = 'unparseable';
523
+ parseCache.goMod = undefined;
524
+ return undefined;
525
+ }
526
+ status.go = 'parsed';
527
+ parseCache.goMod = parsed;
528
+ return parsed;
529
+ }
530
+ catch (err) {
531
+ warnings.push({
532
+ code: 'ADOPT_MANIFEST_UNPARSEABLE',
533
+ message: `go.mod parse failed: ${err.message}`,
534
+ context: { path: 'go.mod' },
535
+ });
536
+ status.go = 'unparseable';
537
+ parseCache.goMod = undefined;
538
+ return undefined;
539
+ }
540
+ }
541
+ function hasDep(name, where, scope = 'all') {
542
+ const kinds = where
543
+ ? Array.isArray(where) ? [...where] : [where]
544
+ : ['npm', 'py', 'cargo', 'go'];
545
+ for (const kind of kinds) {
546
+ if (kind === 'npm' && matchNpmDep(packageJson(), name, scope))
547
+ return true;
548
+ if (kind === 'py' && matchPyDep(pyprojectToml(), name))
549
+ return true;
550
+ if (kind === 'cargo' && matchCargoDep(cargoToml(), name))
551
+ return true;
552
+ if (kind === 'go' && matchGoDep(goMod(), name))
553
+ return true;
554
+ }
555
+ return false;
556
+ }
557
+ function hasAnyDep(names, where, scope = 'all') {
558
+ for (const name of names) {
559
+ if (hasDep(name, where, scope))
560
+ return true;
561
+ }
562
+ return false;
563
+ }
564
+ return {
565
+ projectRoot,
566
+ get warnings() { return warnings; },
567
+ hasFile,
568
+ dirExists,
569
+ rootEntries,
570
+ listDir,
571
+ readFileText,
572
+ manifestStatus,
573
+ packageJson,
574
+ pyprojectToml,
575
+ cargoToml,
576
+ goMod,
577
+ hasDep,
578
+ hasAnyDep,
579
+ };
580
+ }
581
+ export function createFakeSignalContext(input = {}) {
582
+ const warnings = [];
583
+ const rootEntriesCache = [...(input.rootEntries ?? [])].sort();
584
+ const filesMap = input.files ?? {};
585
+ const dirsSet = new Set(input.dirs ?? []);
586
+ function manifestVal(v, _kind) {
587
+ if (v === 'missing' || v === undefined)
588
+ return { val: undefined, status: 'missing' };
589
+ if (v === 'unparseable')
590
+ return { val: undefined, status: 'unparseable' };
591
+ return { val: v, status: 'parsed' };
592
+ }
593
+ const pkg = manifestVal(input.packageJson, 'npm');
594
+ const py = manifestVal(input.pyprojectToml, 'py');
595
+ const cargo = manifestVal(input.cargoToml, 'cargo');
596
+ const go = manifestVal(input.goMod, 'go');
597
+ // Reuses the same module-private matchers as the real context,
598
+ // so fake and real behaviors stay in lockstep.
599
+ function hasDep(name, where, scope = 'all') {
600
+ const kinds = where
601
+ ? Array.isArray(where) ? [...where] : [where]
602
+ : ['npm', 'py', 'cargo', 'go'];
603
+ for (const kind of kinds) {
604
+ if (kind === 'npm' && matchNpmDep(pkg.val, name, scope))
605
+ return true;
606
+ if (kind === 'py' && matchPyDep(py.val, name))
607
+ return true;
608
+ if (kind === 'cargo' && matchCargoDep(cargo.val, name))
609
+ return true;
610
+ if (kind === 'go' && matchGoDep(go.val, name))
611
+ return true;
612
+ }
613
+ return false;
614
+ }
615
+ function hasAnyDep(names, where, scope = 'all') {
616
+ return names.some(n => hasDep(n, where, scope));
617
+ }
618
+ const dirListings = input.dirListings ?? {};
619
+ const statusOverrides = input.manifestStatuses ?? {};
620
+ return {
621
+ projectRoot: input.projectRoot ?? '/fake',
622
+ get warnings() { return warnings; },
623
+ // Only consult filesMap — entries in rootEntries may be directories,
624
+ // and the real context's hasFile() returns false for directory paths.
625
+ // Tests that need "file exists at root" behavior should add the path
626
+ // to `files`, not just `rootEntries`.
627
+ hasFile: (p) => p in filesMap,
628
+ dirExists: (p) => dirsSet.has(p),
629
+ rootEntries: () => rootEntriesCache,
630
+ listDir: (p) => dirListings[p] ?? [],
631
+ readFileText: (p, maxBytes) => {
632
+ const content = filesMap[p];
633
+ if (content === undefined)
634
+ return undefined;
635
+ return maxBytes !== undefined && content.length > maxBytes
636
+ ? content.slice(0, maxBytes)
637
+ : content;
638
+ },
639
+ manifestStatus: (kind) => statusOverrides[kind]
640
+ ?? (kind === 'npm' ? pkg.status
641
+ : kind === 'py' ? py.status
642
+ : kind === 'cargo' ? cargo.status
643
+ : go.status),
644
+ packageJson: () => pkg.val,
645
+ pyprojectToml: () => py.val,
646
+ cargoToml: () => cargo.val,
647
+ goMod: () => go.val,
648
+ hasDep,
649
+ hasAnyDep,
650
+ };
651
+ }
652
+ //# sourceMappingURL=context.js.map