@farmslot/recipe-runner 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/CHANGELOG.md +255 -0
  2. package/LICENSE +21 -0
  3. package/README.md +274 -0
  4. package/bin/farmslot-visual-review.mjs +152 -0
  5. package/dist/adapters/app-lifecycle.d.ts +41 -0
  6. package/dist/adapters/app-lifecycle.d.ts.map +1 -0
  7. package/dist/adapters/app-lifecycle.js +224 -0
  8. package/dist/adapters/app-lifecycle.js.map +1 -0
  9. package/dist/adapters/core.d.ts +5 -0
  10. package/dist/adapters/core.d.ts.map +1 -0
  11. package/dist/adapters/core.js +433 -0
  12. package/dist/adapters/core.js.map +1 -0
  13. package/dist/adapters/gesture.d.ts +12 -0
  14. package/dist/adapters/gesture.d.ts.map +1 -0
  15. package/dist/adapters/gesture.js +76 -0
  16. package/dist/adapters/gesture.js.map +1 -0
  17. package/dist/adapters/ui.d.ts +33 -0
  18. package/dist/adapters/ui.d.ts.map +1 -0
  19. package/dist/adapters/ui.js +130 -0
  20. package/dist/adapters/ui.js.map +1 -0
  21. package/dist/cli/error-output.d.ts +6 -0
  22. package/dist/cli/error-output.d.ts.map +1 -0
  23. package/dist/cli/error-output.js +19 -0
  24. package/dist/cli/error-output.js.map +1 -0
  25. package/dist/cli/index.d.ts +12 -0
  26. package/dist/cli/index.d.ts.map +1 -0
  27. package/dist/cli/index.js +29 -0
  28. package/dist/cli/index.js.map +1 -0
  29. package/dist/cli/run-command.d.ts +8 -0
  30. package/dist/cli/run-command.d.ts.map +1 -0
  31. package/dist/cli/run-command.js +253 -0
  32. package/dist/cli/run-command.js.map +1 -0
  33. package/dist/cli/support.d.ts +31 -0
  34. package/dist/cli/support.d.ts.map +1 -0
  35. package/dist/cli/support.js +297 -0
  36. package/dist/cli/support.js.map +1 -0
  37. package/dist/cli/validate-command.d.ts +4 -0
  38. package/dist/cli/validate-command.d.ts.map +1 -0
  39. package/dist/cli/validate-command.js +52 -0
  40. package/dist/cli/validate-command.js.map +1 -0
  41. package/dist/core/compose.d.ts +23 -0
  42. package/dist/core/compose.d.ts.map +1 -0
  43. package/dist/core/compose.js +104 -0
  44. package/dist/core/compose.js.map +1 -0
  45. package/dist/core/execution.d.ts +44 -0
  46. package/dist/core/execution.d.ts.map +1 -0
  47. package/dist/core/execution.js +304 -0
  48. package/dist/core/execution.js.map +1 -0
  49. package/dist/core/failure.d.ts +23 -0
  50. package/dist/core/failure.d.ts.map +1 -0
  51. package/dist/core/failure.js +43 -0
  52. package/dist/core/failure.js.map +1 -0
  53. package/dist/core/graph.d.ts +12 -0
  54. package/dist/core/graph.d.ts.map +1 -0
  55. package/dist/core/graph.js +65 -0
  56. package/dist/core/graph.js.map +1 -0
  57. package/dist/core/hud.d.ts +11 -0
  58. package/dist/core/hud.d.ts.map +1 -0
  59. package/dist/core/hud.js +120 -0
  60. package/dist/core/hud.js.map +1 -0
  61. package/dist/core/invocation.d.ts +13 -0
  62. package/dist/core/invocation.d.ts.map +1 -0
  63. package/dist/core/invocation.js +68 -0
  64. package/dist/core/invocation.js.map +1 -0
  65. package/dist/core/json.d.ts +9 -0
  66. package/dist/core/json.d.ts.map +1 -0
  67. package/dist/core/json.js +74 -0
  68. package/dist/core/json.js.map +1 -0
  69. package/dist/core/library-manifest.d.ts +53 -0
  70. package/dist/core/library-manifest.d.ts.map +1 -0
  71. package/dist/core/library-manifest.js +263 -0
  72. package/dist/core/library-manifest.js.map +1 -0
  73. package/dist/core/library.d.ts +80 -0
  74. package/dist/core/library.d.ts.map +1 -0
  75. package/dist/core/library.js +321 -0
  76. package/dist/core/library.js.map +1 -0
  77. package/dist/core/parameters.d.ts +5 -0
  78. package/dist/core/parameters.d.ts.map +1 -0
  79. package/dist/core/parameters.js +57 -0
  80. package/dist/core/parameters.js.map +1 -0
  81. package/dist/core/passive-observations.d.ts +25 -0
  82. package/dist/core/passive-observations.d.ts.map +1 -0
  83. package/dist/core/passive-observations.js +62 -0
  84. package/dist/core/passive-observations.js.map +1 -0
  85. package/dist/core/path.d.ts +7 -0
  86. package/dist/core/path.d.ts.map +1 -0
  87. package/dist/core/path.js +147 -0
  88. package/dist/core/path.js.map +1 -0
  89. package/dist/core/recording-cleanup.d.ts +10 -0
  90. package/dist/core/recording-cleanup.d.ts.map +1 -0
  91. package/dist/core/recording-cleanup.js +32 -0
  92. package/dist/core/recording-cleanup.js.map +1 -0
  93. package/dist/core/resolution-error.d.ts +7 -0
  94. package/dist/core/resolution-error.d.ts.map +1 -0
  95. package/dist/core/resolution-error.js +9 -0
  96. package/dist/core/resolution-error.js.map +1 -0
  97. package/dist/core/runner.d.ts +4 -0
  98. package/dist/core/runner.d.ts.map +1 -0
  99. package/dist/core/runner.js +738 -0
  100. package/dist/core/runner.js.map +1 -0
  101. package/dist/core/scroll-to.d.ts +111 -0
  102. package/dist/core/scroll-to.d.ts.map +1 -0
  103. package/dist/core/scroll-to.js +353 -0
  104. package/dist/core/scroll-to.js.map +1 -0
  105. package/dist/core/suite.d.ts +25 -0
  106. package/dist/core/suite.d.ts.map +1 -0
  107. package/dist/core/suite.js +76 -0
  108. package/dist/core/suite.js.map +1 -0
  109. package/dist/core/trace.d.ts +5 -0
  110. package/dist/core/trace.d.ts.map +1 -0
  111. package/dist/core/trace.js +25 -0
  112. package/dist/core/trace.js.map +1 -0
  113. package/dist/core/trust-error.d.ts +9 -0
  114. package/dist/core/trust-error.d.ts.map +1 -0
  115. package/dist/core/trust-error.js +18 -0
  116. package/dist/core/trust-error.js.map +1 -0
  117. package/dist/core/trust-input.d.ts +20 -0
  118. package/dist/core/trust-input.d.ts.map +1 -0
  119. package/dist/core/trust-input.js +68 -0
  120. package/dist/core/trust-input.js.map +1 -0
  121. package/dist/core/trust.d.ts +28 -0
  122. package/dist/core/trust.d.ts.map +1 -0
  123. package/dist/core/trust.js +284 -0
  124. package/dist/core/trust.js.map +1 -0
  125. package/dist/core/types.d.ts +319 -0
  126. package/dist/core/types.d.ts.map +1 -0
  127. package/dist/core/types.js +2 -0
  128. package/dist/core/types.js.map +1 -0
  129. package/dist/core/validation.d.ts +4 -0
  130. package/dist/core/validation.d.ts.map +1 -0
  131. package/dist/core/validation.js +18 -0
  132. package/dist/core/validation.js.map +1 -0
  133. package/dist/index.d.ts +32 -0
  134. package/dist/index.d.ts.map +1 -0
  135. package/dist/index.js +25 -0
  136. package/dist/index.js.map +1 -0
  137. package/dist/node/writers.d.ts +27 -0
  138. package/dist/node/writers.d.ts.map +1 -0
  139. package/dist/node/writers.js +99 -0
  140. package/dist/node/writers.js.map +1 -0
  141. package/dist/recording/android-mirror.d.ts +10 -0
  142. package/dist/recording/android-mirror.d.ts.map +1 -0
  143. package/dist/recording/android-mirror.js +180 -0
  144. package/dist/recording/android-mirror.js.map +1 -0
  145. package/dist/recording/capture-helper-timing.d.ts +4 -0
  146. package/dist/recording/capture-helper-timing.d.ts.map +1 -0
  147. package/dist/recording/capture-helper-timing.js +47 -0
  148. package/dist/recording/capture-helper-timing.js.map +1 -0
  149. package/dist/recording/capture-helper.d.ts +10 -0
  150. package/dist/recording/capture-helper.d.ts.map +1 -0
  151. package/dist/recording/capture-helper.js +335 -0
  152. package/dist/recording/capture-helper.js.map +1 -0
  153. package/dist/recording/cdp-video-recorder.d.ts +9 -0
  154. package/dist/recording/cdp-video-recorder.d.ts.map +1 -0
  155. package/dist/recording/cdp-video-recorder.js +319 -0
  156. package/dist/recording/cdp-video-recorder.js.map +1 -0
  157. package/dist/recording/frame-sampler.d.ts +19 -0
  158. package/dist/recording/frame-sampler.d.ts.map +1 -0
  159. package/dist/recording/frame-sampler.js +29 -0
  160. package/dist/recording/frame-sampler.js.map +1 -0
  161. package/dist/recording/timeline.d.ts +15 -0
  162. package/dist/recording/timeline.d.ts.map +1 -0
  163. package/dist/recording/timeline.js +88 -0
  164. package/dist/recording/timeline.js.map +1 -0
  165. package/dist/runtime/browser-extension.d.ts +17 -0
  166. package/dist/runtime/browser-extension.d.ts.map +1 -0
  167. package/dist/runtime/browser-extension.js +42 -0
  168. package/dist/runtime/browser-extension.js.map +1 -0
  169. package/dist/runtime/cdp.d.ts +138 -0
  170. package/dist/runtime/cdp.d.ts.map +1 -0
  171. package/dist/runtime/cdp.js +1429 -0
  172. package/dist/runtime/cdp.js.map +1 -0
  173. package/dist/runtime/decision-types.d.ts +23 -0
  174. package/dist/runtime/decision-types.d.ts.map +1 -0
  175. package/dist/runtime/decision-types.js +2 -0
  176. package/dist/runtime/decision-types.js.map +1 -0
  177. package/dist/runtime/deps-readiness.d.ts +21 -0
  178. package/dist/runtime/deps-readiness.d.ts.map +1 -0
  179. package/dist/runtime/deps-readiness.js +141 -0
  180. package/dist/runtime/deps-readiness.js.map +1 -0
  181. package/dist/runtime/log-analysis.d.ts +48 -0
  182. package/dist/runtime/log-analysis.d.ts.map +1 -0
  183. package/dist/runtime/log-analysis.js +128 -0
  184. package/dist/runtime/log-analysis.js.map +1 -0
  185. package/dist/runtime/metro-probe.d.ts +5 -0
  186. package/dist/runtime/metro-probe.d.ts.map +1 -0
  187. package/dist/runtime/metro-probe.js +24 -0
  188. package/dist/runtime/metro-probe.js.map +1 -0
  189. package/dist/runtime/operation.d.ts +32 -0
  190. package/dist/runtime/operation.d.ts.map +1 -0
  191. package/dist/runtime/operation.js +184 -0
  192. package/dist/runtime/operation.js.map +1 -0
  193. package/dist/runtime/orchestrate-up.d.ts +22 -0
  194. package/dist/runtime/orchestrate-up.d.ts.map +1 -0
  195. package/dist/runtime/orchestrate-up.js +35 -0
  196. package/dist/runtime/orchestrate-up.js.map +1 -0
  197. package/dist/runtime/react-native-bridge.d.ts +17 -0
  198. package/dist/runtime/react-native-bridge.d.ts.map +1 -0
  199. package/dist/runtime/react-native-bridge.js +57 -0
  200. package/dist/runtime/react-native-bridge.js.map +1 -0
  201. package/dist/version.d.ts +2 -0
  202. package/dist/version.d.ts.map +1 -0
  203. package/dist/version.js +8 -0
  204. package/dist/version.js.map +1 -0
  205. package/package.json +163 -0
  206. package/test/fixtures/scroll-to-visible/README.md +39 -0
  207. package/visual-review/feedback-draft.mjs +82 -0
  208. package/visual-review/index.d.mts +41 -0
  209. package/visual-review/index.mjs +4 -0
  210. package/visual-review/recipe-board.mjs +142 -0
  211. package/visual-review/review-board.mjs +981 -0
  212. package/visual-review/server.mjs +69 -0
@@ -0,0 +1,13 @@
1
+ export interface RecipeInvocationDocument {
2
+ version: 1;
3
+ recipeDigest: string;
4
+ startedAt: string;
5
+ params: Record<string, unknown>;
6
+ redactedPaths: string[];
7
+ }
8
+ /** Retain typed public inputs without recording credential-bearing fields. */
9
+ export declare function redactRecipeParams(params: Record<string, unknown>, schema: unknown): Pick<RecipeInvocationDocument, 'params' | 'redactedPaths'>;
10
+ export declare function createRecipeInvocation(recipe: unknown, params: Record<string, unknown>, startedAt: string): RecipeInvocationDocument;
11
+ /** Reject mismatched or redacted inputs instead of validating a different execution. */
12
+ export declare function recordedRecipeParams(recipe: unknown, invocation: unknown, summary: unknown): Record<string, unknown>;
13
+ //# sourceMappingURL=invocation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invocation.d.ts","sourceRoot":"","sources":["../../src/core/invocation.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,CAAC,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,8EAA8E;AAC9E,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,MAAM,EAAE,OAAO,GACd,IAAI,CAAC,wBAAwB,EAAE,QAAQ,GAAG,eAAe,CAAC,CA0C5D;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,SAAS,EAAE,MAAM,GAChB,wBAAwB,CAO1B;AAED,wFAAwF;AACxF,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,OAAO,EACf,UAAU,EAAE,OAAO,EACnB,OAAO,EAAE,OAAO,GACf,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAyBzB"}
@@ -0,0 +1,68 @@
1
+ import { digestRecipeDocument, isRecord } from '@farmslot/protocol';
2
+ const SENSITIVE_KEY = /(?:api[-_]?key|auth|credential|mnemonic|pass(?:word)?|private[-_]?key|secret|seed|srp|token|vault)/iu;
3
+ const PUBLIC_KEY_INDEX = /^(?:api_key_index|apiKeyIndex)$/u;
4
+ /** Retain typed public inputs without recording credential-bearing fields. */
5
+ export function redactRecipeParams(params, schema) {
6
+ const redactedPaths = [];
7
+ function visit(value, definition, pointer) {
8
+ if (Array.isArray(value)) {
9
+ return value.map((entry, index) => visit(entry, isRecord(definition) ? definition.items : undefined, `${pointer}/${index}`));
10
+ }
11
+ if (isRecord(value)) {
12
+ const properties = isRecord(definition) && isRecord(definition.properties) ? definition.properties : {};
13
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => {
14
+ const child = properties[key];
15
+ const childPath = `${pointer}/${key.replace(/~/gu, '~0').replace(/\//gu, '~1')}`;
16
+ const publicIndex = PUBLIC_KEY_INDEX.test(key) &&
17
+ isRecord(child) &&
18
+ child.type === 'integer' &&
19
+ Number.isSafeInteger(entry) &&
20
+ Number(entry) >= 0;
21
+ if (SENSITIVE_KEY.test(key) && !publicIndex) {
22
+ redactedPaths.push(childPath);
23
+ return [key, '<redacted>'];
24
+ }
25
+ return [key, visit(entry, child, childPath)];
26
+ }));
27
+ }
28
+ if (typeof value === 'string') {
29
+ const redacted = value
30
+ .replace(/(\w+:\/\/)[^/@\s:]+:[^/@\s]+@/gu, '$1<redacted>@')
31
+ .replace(/((?:api[-_]?key|auth|credential|mnemonic|pass(?:word)?|private[-_]?key|secret|seed|srp|token|vault)=)[^\s]+/giu, '$1<redacted>');
32
+ if (redacted !== value)
33
+ redactedPaths.push(pointer);
34
+ return redacted;
35
+ }
36
+ return value;
37
+ }
38
+ return { params: visit(params, schema, ''), redactedPaths };
39
+ }
40
+ export function createRecipeInvocation(recipe, params, startedAt) {
41
+ return {
42
+ version: 1,
43
+ recipeDigest: digestRecipeDocument(recipe),
44
+ startedAt,
45
+ ...redactRecipeParams(params, isRecord(recipe) ? recipe.paramsSchema : undefined),
46
+ };
47
+ }
48
+ /** Reject mismatched or redacted inputs instead of validating a different execution. */
49
+ export function recordedRecipeParams(recipe, invocation, summary) {
50
+ if (!isRecord(invocation) ||
51
+ invocation.version !== 1 ||
52
+ !isRecord(invocation.params) ||
53
+ !Array.isArray(invocation.redactedPaths) ||
54
+ !invocation.redactedPaths.every((entry) => typeof entry === 'string') ||
55
+ !isRecord(summary) ||
56
+ typeof invocation.startedAt !== 'string' ||
57
+ invocation.startedAt !== summary.startedAt ||
58
+ invocation.recipeDigest !== digestRecipeDocument(recipe) ||
59
+ summary.invocationDigest !== digestRecipeDocument(invocation)) {
60
+ throw new Error('Recipe invocation does not match the recorded recipe and execution summary.');
61
+ }
62
+ const safe = redactRecipeParams(invocation.params, isRecord(recipe) ? recipe.paramsSchema : undefined);
63
+ if (invocation.redactedPaths.length || safe.redactedPaths.length) {
64
+ throw new Error('Recipe invocation contains redacted parameters; use fixture references instead of credentials in recipe inputs.');
65
+ }
66
+ return invocation.params;
67
+ }
68
+ //# sourceMappingURL=invocation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invocation.js","sourceRoot":"","sources":["../../src/core/invocation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export declare function readJsonFile(filePath: string): Promise<unknown>;
2
+ export declare function writeJsonFile(filePath: string, value: unknown): Promise<void>;
3
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
4
+ export declare function asString(value: unknown, label: string): string;
5
+ export declare function asOptionalString(value: unknown, label: string): string | undefined;
6
+ export declare function asNumber(value: unknown, label: string): number;
7
+ export declare function normalizeRelativePath(relativePath: string): string;
8
+ export declare function getJsonPathValue(value: unknown, jsonPath: string): unknown;
9
+ //# sourceMappingURL=json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/core/json.ts"],"names":[],"mappings":"AAGA,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQrE;AAED,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAGnF;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAK9D;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGlF;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAK9D;AAED,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CASlE;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAuB1E"}
@@ -0,0 +1,74 @@
1
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ export async function readJsonFile(filePath) {
4
+ const text = await readFile(filePath, 'utf-8');
5
+ try {
6
+ return JSON.parse(text);
7
+ }
8
+ catch (error) {
9
+ const message = error instanceof Error ? error.message : String(error);
10
+ throw new Error(`Failed to parse JSON file ${filePath}: ${message}`);
11
+ }
12
+ }
13
+ export async function writeJsonFile(filePath, value) {
14
+ await mkdir(path.dirname(filePath), { recursive: true });
15
+ await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf-8');
16
+ }
17
+ export function isRecord(value) {
18
+ return Boolean(value && typeof value === 'object' && !Array.isArray(value));
19
+ }
20
+ export function asString(value, label) {
21
+ if (typeof value !== 'string' || value.trim().length === 0) {
22
+ throw new Error(`${label} must be a non-empty string.`);
23
+ }
24
+ return value;
25
+ }
26
+ export function asOptionalString(value, label) {
27
+ if (value == null)
28
+ return undefined;
29
+ return asString(value, label);
30
+ }
31
+ export function asNumber(value, label) {
32
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
33
+ throw new Error(`${label} must be a finite number.`);
34
+ }
35
+ return value;
36
+ }
37
+ export function normalizeRelativePath(relativePath) {
38
+ if (path.isAbsolute(relativePath) || /^[A-Za-z]:/.test(relativePath)) {
39
+ throw new Error(`Path ${relativePath} must be relative.`);
40
+ }
41
+ const normalized = path.normalize(relativePath);
42
+ if (normalized === '..' || normalized.startsWith(`..${path.sep}`)) {
43
+ throw new Error(`Path ${relativePath} must not escape its root.`);
44
+ }
45
+ return normalized;
46
+ }
47
+ export function getJsonPathValue(value, jsonPath) {
48
+ if (jsonPath === '$')
49
+ return value;
50
+ if (!jsonPath.startsWith('$.')) {
51
+ throw new Error(`Only simple $.path JSON paths are supported, received ${jsonPath}.`);
52
+ }
53
+ const parts = jsonPath
54
+ .slice(2)
55
+ .replaceAll('[', '.')
56
+ .replaceAll(']', '')
57
+ .split('.')
58
+ .filter(Boolean);
59
+ let current = value;
60
+ for (const part of parts) {
61
+ if (Array.isArray(current)) {
62
+ const index = Number(part);
63
+ if (!Number.isInteger(index))
64
+ return undefined;
65
+ current = current[index];
66
+ continue;
67
+ }
68
+ if (!isRecord(current))
69
+ return undefined;
70
+ current = current[part];
71
+ }
72
+ return current;
73
+ }
74
+ //# sourceMappingURL=json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/core/json.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,QAAgB;IACjD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC/C,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,MAAM,IAAI,KAAK,CAAC,6BAA6B,QAAQ,KAAK,OAAO,EAAE,CAAC,CAAC;IACvE,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,QAAgB,EAAE,KAAc;IAClE,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzD,MAAM,SAAS,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAC5E,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,OAAO,OAAO,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;AAC9E,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,KAAc,EAAE,KAAa;IACpD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,8BAA8B,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAc,EAAE,KAAa;IAC5D,IAAI,KAAK,IAAI,IAAI;QAAE,OAAO,SAAS,CAAC;IACpC,OAAO,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,KAAc,EAAE,KAAa;IACpD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,2BAA2B,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,YAAoB;IACxD,IAAI,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;QACrE,MAAM,IAAI,KAAK,CAAC,QAAQ,YAAY,oBAAoB,CAAC,CAAC;IAC5D,CAAC;IACD,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;IAChD,IAAI,UAAU,KAAK,IAAI,IAAI,UAAU,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC;QAClE,MAAM,IAAI,KAAK,CAAC,QAAQ,YAAY,4BAA4B,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAc,EAAE,QAAgB;IAC/D,IAAI,QAAQ,KAAK,GAAG;QAAE,OAAO,KAAK,CAAC;IACnC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,yDAAyD,QAAQ,GAAG,CAAC,CAAC;IACxF,CAAC;IACD,MAAM,KAAK,GAAG,QAAQ;SACnB,KAAK,CAAC,CAAC,CAAC;SACR,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC;SACpB,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC;SACnB,KAAK,CAAC,GAAG,CAAC;SACV,MAAM,CAAC,OAAO,CAAC,CAAC;IACnB,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3B,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;YAC3B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC;gBAAE,OAAO,SAAS,CAAC;YAC/C,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;YACzB,SAAS;QACX,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC;YAAE,OAAO,SAAS,CAAC;QACzC,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,53 @@
1
+ export declare const RECIPE_LIBRARY_MANIFEST_FILE = "recipe-library.json";
2
+ /** Recipe folder scope that applies to every platform. */
3
+ export declare const SHARED_RECIPE_SCOPE = "shared";
4
+ /** Library folders that discovery and runs read; the digest covers exactly these plus declared files. */
5
+ export declare const RECIPE_LIBRARY_DIRECTORIES: readonly ["recipes", "manifests", "actions"];
6
+ export interface RecipeLibraryAdapterDeclaration {
7
+ /** Module path relative to the library root. */
8
+ module: string;
9
+ /** Named export holding the adapter; defaults to the module's default export. */
10
+ export?: string;
11
+ /** Platform adapter id this adapter extends. */
12
+ extends?: string;
13
+ }
14
+ /** `recipe-library.json`. Every key is optional; unknown keys are ignored for forward compatibility. */
15
+ export interface RecipeLibraryManifest {
16
+ /** Platform variant folders under recipes/, in addition to the built-in platforms. */
17
+ platforms?: string[];
18
+ /** Platform adapter plugins this library ships. Declared and digested; not loaded yet. */
19
+ adapters?: Record<string, RecipeLibraryAdapterDeclaration>;
20
+ /** Action manifest files keyed by platform or `shared`, relative to the library root. */
21
+ actions?: Record<string, string>;
22
+ /** Package version ranges this library needs, e.g. `{ "@farmslot/recipe-cli": ">=0.1.0" }`. */
23
+ requires?: Record<string, string>;
24
+ }
25
+ /** Installed package versions a host provides for checking `requires`. */
26
+ export type RecipePackageVersions = Readonly<Record<string, string>>;
27
+ export interface RecipeLibraryRequirement {
28
+ package: string;
29
+ range: string;
30
+ installed: string;
31
+ }
32
+ /** Real path of a configured library root; a missing root is a configuration error, not a crash. */
33
+ export declare function libraryRootReal(root: string): Promise<string>;
34
+ /** Read and validate a library's recipe-library.json; undefined when the library has none. */
35
+ export declare function readRecipeLibraryManifest(root: string): Promise<RecipeLibraryManifest | undefined>;
36
+ /**
37
+ * Check a library's `requires` against the versions the host provides. Fails closed: a package
38
+ * the host cannot vouch for is as unsatisfied as an out-of-range version.
39
+ */
40
+ export declare function checkRecipeLibraryRequirements(library: string, manifest: RecipeLibraryManifest | undefined, packageVersions?: RecipePackageVersions): RecipeLibraryRequirement[];
41
+ /**
42
+ * Regular files under `<root>/<directory>`, as sorted `/`-separated paths relative to the root.
43
+ * The one walker for loading and digesting: it skips dot-entries, `node_modules` and symlinked
44
+ * directories, and rejects any file that resolves outside the library root.
45
+ */
46
+ export declare function listLibraryFiles(root: string, directory: string): Promise<string[]>;
47
+ /**
48
+ * Content digest of what a library contributes to discovery and runs: recipe-library.json,
49
+ * recipes/, manifests/, actions/, and the files it declares. Text files hash with LF line
50
+ * endings, so a CRLF checkout of the same content has the same digest.
51
+ */
52
+ export declare function digestRecipeLibrary(root: string, manifest?: RecipeLibraryManifest): Promise<string>;
53
+ //# sourceMappingURL=library-manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"library-manifest.d.ts","sourceRoot":"","sources":["../../src/core/library-manifest.ts"],"names":[],"mappings":"AAkBA,eAAO,MAAM,4BAA4B,wBAAwB,CAAC;AAClE,0DAA0D;AAC1D,eAAO,MAAM,mBAAmB,WAAW,CAAC;AAG5C,yGAAyG;AACzG,eAAO,MAAM,0BAA0B,8CAA+C,CAAC;AAEvF,MAAM,WAAW,+BAA+B;IAC9C,gDAAgD;IAChD,MAAM,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wGAAwG;AACxG,MAAM,WAAW,qBAAqB;IACpC,sFAAsF;IACtF,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,+BAA+B,CAAC,CAAC;IAC3D,yFAAyF;IACzF,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED,0EAA0E;AAC1E,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAErE,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,oGAAoG;AACpG,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAWnE;AAED,8FAA8F;AAC9F,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,qBAAqB,GAAG,SAAS,CAAC,CA0F5C;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,qBAAqB,GAAG,SAAS,EAC3C,eAAe,GAAE,qBAA0B,GAC1C,wBAAwB,EAAE,CAuB5B;AAgCD;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CA4CzF;AAID;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,qBAAqB,GAC/B,OAAO,CAAC,MAAM,CAAC,CA4BjB"}
@@ -0,0 +1,263 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readdir, readFile, realpath, stat } from 'node:fs/promises';
3
+ import { createRequire } from 'node:module';
4
+ import path from 'node:path';
5
+ import { RECIPE_RUNNER_VERSION } from '../version.js';
6
+ import { isRecord } from './json.js';
7
+ import { isPathWithin } from './path.js';
8
+ import { RecipeResolutionError } from './resolution-error.js';
9
+ import { invalidRecipeSource } from './trust-error.js';
10
+ const require = createRequire(import.meta.url);
11
+ const semver = require('semver');
12
+ export const RECIPE_LIBRARY_MANIFEST_FILE = 'recipe-library.json';
13
+ /** Recipe folder scope that applies to every platform. */
14
+ export const SHARED_RECIPE_SCOPE = 'shared';
15
+ const PLATFORM_ID = /^[a-z][a-z0-9-]*$/u;
16
+ /** Library folders that discovery and runs read; the digest covers exactly these plus declared files. */
17
+ export const RECIPE_LIBRARY_DIRECTORIES = ['recipes', 'manifests', 'actions'];
18
+ /** Real path of a configured library root; a missing root is a configuration error, not a crash. */
19
+ export async function libraryRootReal(root) {
20
+ try {
21
+ return await realpath(root);
22
+ }
23
+ catch (error) {
24
+ if (error.code !== 'ENOENT')
25
+ throw error;
26
+ throw new RecipeResolutionError('RECIPE_LIBRARY_PATH_INVALID', `Recipe library root ${root} does not exist.`, 'fix the --library or RECIPE_LIBRARY_PATH entry so it names a library directory');
27
+ }
28
+ }
29
+ /** Read and validate a library's recipe-library.json; undefined when the library has none. */
30
+ export async function readRecipeLibraryManifest(root) {
31
+ const rootReal = await libraryRootReal(root);
32
+ const file = path.join(root, RECIPE_LIBRARY_MANIFEST_FILE);
33
+ let manifestReal;
34
+ try {
35
+ manifestReal = await realpath(file);
36
+ }
37
+ catch (error) {
38
+ // A library without recipe-library.json uses the folder conventions only.
39
+ if (error.code === 'ENOENT')
40
+ return undefined;
41
+ throw error;
42
+ }
43
+ if (!isPathWithin(rootReal, manifestReal))
44
+ throw invalidRecipeSource('Recipe library manifest resolves outside its root.', 'move recipe-library.json inside its library root');
45
+ const invalid = (detail) => new RecipeResolutionError('RECIPE_LIBRARY_MANIFEST_INVALID', `${file} is invalid: ${detail}`, `fix ${RECIPE_LIBRARY_MANIFEST_FILE} in ${root}`);
46
+ if (!(await stat(manifestReal)).isFile())
47
+ throw invalid('it is not a regular file.');
48
+ let value;
49
+ try {
50
+ value = JSON.parse(await readFile(manifestReal, 'utf8'));
51
+ }
52
+ catch (error) {
53
+ if (!(error instanceof SyntaxError))
54
+ throw error;
55
+ throw invalid(`not valid JSON (${error.message}).`);
56
+ }
57
+ if (!isRecord(value))
58
+ throw invalid('it must contain a JSON object.');
59
+ const manifest = {};
60
+ if (value.platforms !== undefined) {
61
+ if (!Array.isArray(value.platforms) ||
62
+ value.platforms.some((platform) => typeof platform !== 'string' ||
63
+ !PLATFORM_ID.test(platform) ||
64
+ platform === SHARED_RECIPE_SCOPE))
65
+ throw invalid('platforms must be an array of platform ids.');
66
+ manifest.platforms = [...value.platforms];
67
+ }
68
+ if (value.adapters !== undefined) {
69
+ if (!isRecord(value.adapters))
70
+ throw invalid('adapters must be an object.');
71
+ manifest.adapters = {};
72
+ for (const [id, entry] of Object.entries(value.adapters)) {
73
+ if (!PLATFORM_ID.test(id))
74
+ throw invalid(`adapter id ${id} is not a platform id.`);
75
+ if (!isRecord(entry) || typeof entry.module !== 'string' || !entry.module.trim())
76
+ throw invalid(`adapters.${id}.module must be a path.`);
77
+ for (const key of ['export', 'extends']) {
78
+ if (entry[key] !== undefined && (typeof entry[key] !== 'string' || !entry[key].trim()))
79
+ throw invalid(`adapters.${id}.${key} must be a non-empty string.`);
80
+ }
81
+ await declaredLibraryFile(rootReal, root, entry.module, `adapters.${id}.module`, invalid);
82
+ manifest.adapters[id] = {
83
+ module: entry.module,
84
+ ...(typeof entry.export === 'string' ? { export: entry.export } : {}),
85
+ ...(typeof entry.extends === 'string' ? { extends: entry.extends } : {}),
86
+ };
87
+ }
88
+ }
89
+ if (value.actions !== undefined) {
90
+ if (!isRecord(value.actions))
91
+ throw invalid('actions must be an object.');
92
+ manifest.actions = {};
93
+ for (const [scope, manifestPath] of Object.entries(value.actions)) {
94
+ if (!PLATFORM_ID.test(scope))
95
+ throw invalid(`actions key ${scope} must be a platform id or shared.`);
96
+ if (typeof manifestPath !== 'string' || !manifestPath.trim())
97
+ throw invalid(`actions.${scope} must be a path.`);
98
+ await declaredLibraryFile(rootReal, root, manifestPath, `actions.${scope}`, invalid);
99
+ manifest.actions[scope] = manifestPath;
100
+ }
101
+ }
102
+ if (value.requires !== undefined) {
103
+ if (!isRecord(value.requires))
104
+ throw invalid('requires must be an object.');
105
+ manifest.requires = {};
106
+ for (const [name, range] of Object.entries(value.requires)) {
107
+ if (typeof range !== 'string' || semver.validRange(range) === null)
108
+ throw invalid(`requires.${name} must be a semver range.`);
109
+ manifest.requires[name] = range;
110
+ }
111
+ }
112
+ return manifest;
113
+ }
114
+ /**
115
+ * Check a library's `requires` against the versions the host provides. Fails closed: a package
116
+ * the host cannot vouch for is as unsatisfied as an out-of-range version.
117
+ */
118
+ export function checkRecipeLibraryRequirements(library, manifest, packageVersions = {}) {
119
+ const installed = {
120
+ '@farmslot/recipe-runner': RECIPE_RUNNER_VERSION,
121
+ ...packageVersions,
122
+ };
123
+ return Object.entries(manifest?.requires ?? {}).map(([name, range]) => {
124
+ const version = installed[name];
125
+ if (version === undefined) {
126
+ throw new RecipeResolutionError('RECIPE_LIBRARY_REQUIREMENT_UNSATISFIED', `Library ${library} requires ${name} ${range}, which this host cannot provide or check.`, `use a CLI that provides ${name}, or remove ${name} from the requires of ${library}`);
127
+ }
128
+ if (!semver.satisfies(version, range)) {
129
+ throw new RecipeResolutionError('RECIPE_LIBRARY_REQUIREMENT_UNSATISFIED', `Library ${library} requires ${name} ${range}; this host has ${version}.`, `upgrade ${name} to a version matching ${range}, or pin an older ${library} library`);
130
+ }
131
+ return { package: name, range, installed: version };
132
+ });
133
+ }
134
+ /** Resolve a path declared by recipe-library.json; it must be a regular file inside the library. */
135
+ async function declaredLibraryFile(rootReal, root, relativePath, key, invalid) {
136
+ if (path.isAbsolute(relativePath))
137
+ throw invalidRecipeSource(`${RECIPE_LIBRARY_MANIFEST_FILE} ${key} must be relative to the library root.`, `make ${key} a path inside ${root}`);
138
+ let fileReal;
139
+ try {
140
+ fileReal = await realpath(path.resolve(root, relativePath));
141
+ }
142
+ catch (error) {
143
+ if (error.code !== 'ENOENT')
144
+ throw error;
145
+ throw invalid(`${key} names ${relativePath}, which does not exist.`);
146
+ }
147
+ if (!isPathWithin(rootReal, fileReal))
148
+ throw invalidRecipeSource(`${RECIPE_LIBRARY_MANIFEST_FILE} ${key} resolves outside its library root.`, `move ${relativePath} inside ${root}`);
149
+ if (!(await stat(fileReal)).isFile())
150
+ throw invalid(`${key} names ${relativePath}, which is not a regular file.`);
151
+ return fileReal;
152
+ }
153
+ /**
154
+ * Regular files under `<root>/<directory>`, as sorted `/`-separated paths relative to the root.
155
+ * The one walker for loading and digesting: it skips dot-entries, `node_modules` and symlinked
156
+ * directories, and rejects any file that resolves outside the library root.
157
+ */
158
+ export async function listLibraryFiles(root, directory) {
159
+ const rootReal = await libraryRootReal(root);
160
+ const visit = async (relativeDir) => {
161
+ let entries;
162
+ try {
163
+ entries = await readdir(path.join(root, relativeDir), { withFileTypes: true });
164
+ }
165
+ catch (error) {
166
+ // Every library folder is optional.
167
+ if (error.code === 'ENOENT' && relativeDir === directory)
168
+ return [];
169
+ throw error;
170
+ }
171
+ // Entries are checked concurrently: discovery walks a library several times per command.
172
+ const nested = await Promise.all(entries.map(async (entry) => {
173
+ if (entry.name.startsWith('.') || entry.name === 'node_modules')
174
+ return [];
175
+ const relativePath = path.join(relativeDir, entry.name);
176
+ if (entry.isDirectory())
177
+ return visit(relativePath);
178
+ if (!entry.isFile() && !entry.isSymbolicLink())
179
+ return [];
180
+ const portable = relativePath.split(path.sep).join('/');
181
+ let fileReal;
182
+ try {
183
+ fileReal = await realpath(path.join(root, relativePath));
184
+ }
185
+ catch (error) {
186
+ if (error.code !== 'ENOENT')
187
+ throw error;
188
+ throw invalidRecipeSource(`Library file ${portable} is a broken symlink.`, 'remove the symlink or restore its target inside the library root');
189
+ }
190
+ // Symlinked directories are not followed, so a link cannot pull another tree in.
191
+ if (entry.isSymbolicLink() && !(await stat(fileReal)).isFile())
192
+ return [];
193
+ if (!isPathWithin(rootReal, fileReal)) {
194
+ throw invalidRecipeSource(`Library file ${portable} resolves outside its library root.`, 'move the file inside the library root or remove the escaping symlink');
195
+ }
196
+ return [portable];
197
+ }));
198
+ return nested.flat();
199
+ };
200
+ return (await visit(directory)).sort();
201
+ }
202
+ const fileDigests = new Map();
203
+ /**
204
+ * Content digest of what a library contributes to discovery and runs: recipe-library.json,
205
+ * recipes/, manifests/, actions/, and the files it declares. Text files hash with LF line
206
+ * endings, so a CRLF checkout of the same content has the same digest.
207
+ */
208
+ export async function digestRecipeLibrary(root, manifest) {
209
+ const files = new Set();
210
+ const declared = [
211
+ RECIPE_LIBRARY_MANIFEST_FILE,
212
+ ...Object.values(manifest?.actions ?? {}),
213
+ ...Object.values(manifest?.adapters ?? {}).map((adapter) => adapter.module),
214
+ ];
215
+ const rootReal = await libraryRootReal(root);
216
+ for (const file of declared) {
217
+ const relative = path.relative(root, path.resolve(root, file)).split(path.sep).join('/');
218
+ // Declared files are checked here too, so the digest never covers content outside the library.
219
+ const fileReal = await existingRealpath(path.join(root, relative));
220
+ if (!fileReal)
221
+ continue;
222
+ if (!isPathWithin(rootReal, fileReal))
223
+ throw invalidRecipeSource(`Library file ${relative} resolves outside its library root.`, 'move the file inside the library root or remove the escaping symlink');
224
+ if ((await stat(fileReal)).isFile())
225
+ files.add(relative);
226
+ }
227
+ for (const directory of RECIPE_LIBRARY_DIRECTORIES) {
228
+ for (const file of await listLibraryFiles(root, directory))
229
+ files.add(file);
230
+ }
231
+ const sorted = [...files].sort();
232
+ const digests = await Promise.all(sorted.map((file) => fileDigest(path.join(root, file))));
233
+ const hash = createHash('sha256');
234
+ sorted.forEach((file, index) => hash.update(`${file}\0${digests[index]}\n`));
235
+ return `sha256:${hash.digest('hex')}`;
236
+ }
237
+ /** Per-file content hash, cached per process while size and mtime are unchanged. */
238
+ async function fileDigest(file) {
239
+ const info = await stat(file);
240
+ const cached = fileDigests.get(file);
241
+ if (cached && cached.mtimeMs === info.mtimeMs && cached.size === info.size)
242
+ return cached.digest;
243
+ const content = await readFile(file);
244
+ // Binary files hash byte-exact; text files hash with normalized line endings.
245
+ const normalized = content.includes(0)
246
+ ? content
247
+ : Buffer.from(content.toString('utf8').replaceAll('\r\n', '\n'), 'utf8');
248
+ const digest = createHash('sha256').update(normalized).digest('hex');
249
+ fileDigests.set(file, { mtimeMs: info.mtimeMs, size: info.size, digest });
250
+ return digest;
251
+ }
252
+ async function existingRealpath(file) {
253
+ try {
254
+ return await realpath(file);
255
+ }
256
+ catch (error) {
257
+ // recipe-library.json is optional; a missing declared file was already rejected by the reader.
258
+ if (error.code === 'ENOENT')
259
+ return undefined;
260
+ throw error;
261
+ }
262
+ }
263
+ //# sourceMappingURL=library-manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"library-manifest.js","sourceRoot":"","sources":["../../src/core/library-manifest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,80 @@
1
+ import { type RecipeSourceProvenance } from '@farmslot/protocol';
2
+ import { type RecipePackageVersions } from './library-manifest.js';
3
+ import type { LoadedRecipeLibrarySource, RecipeLibrarySource, RecipeLogger } from './types.js';
4
+ /** Platforms whose variant folders every library recognizes without declaring them. */
5
+ export declare const BUILT_IN_RECIPE_PLATFORMS: readonly string[];
6
+ /** Platform variant folders a library recognizes: the built-in set plus its declared platforms. */
7
+ export declare function listRecipeLibraryPlatforms(root: string): Promise<string[]>;
8
+ export interface RecipeLibraryLoadOptions {
9
+ /** Active platform: selects platform variants. */
10
+ adapter?: string;
11
+ logger?: RecipeLogger;
12
+ /** Package versions the host provides for checking each library's `requires`. */
13
+ packageVersions?: RecipePackageVersions;
14
+ }
15
+ export type RecipeLibraryEnv = Record<string, string | undefined>;
16
+ export interface ResolvedLibraryRecipe {
17
+ ref: string;
18
+ document: Record<string, unknown>;
19
+ source: string;
20
+ /** Recipe file path relative to the library root. */
21
+ file: string;
22
+ path: string;
23
+ adapter?: string;
24
+ provenance: RecipeSourceProvenance;
25
+ /** Source names that also declare this ref at lower precedence. */
26
+ shadows: string[];
27
+ /** Qualified compatibility reference; excluded from catalogs. */
28
+ aliasFor?: string;
29
+ }
30
+ export interface RecipeLibraryResolution {
31
+ sources: LoadedRecipeLibrarySource[];
32
+ recipes: ReadonlyMap<string, ResolvedLibraryRecipe>;
33
+ }
34
+ export declare function parseRecipeLibraryPath(value: string): RecipeLibrarySource[];
35
+ export declare function personalRecipeLibraryRoot(env?: RecipeLibraryEnv): string;
36
+ export declare function defaultRecipeLibrarySources(env?: RecipeLibraryEnv): Promise<RecipeLibrarySource[]>;
37
+ /**
38
+ * Ordered library sources: --library entries, then RECIPE_LIBRARY_PATH, or the personal
39
+ * library when neither is set. A --library entry replaces the RECIPE_LIBRARY_PATH entry
40
+ * with the same name instead of conflicting with it.
41
+ */
42
+ export declare function resolveRecipeLibrarySources(options?: {
43
+ cliEntries?: string[];
44
+ env?: RecipeLibraryEnv;
45
+ recipePath?: string;
46
+ }): Promise<RecipeLibrarySource[]>;
47
+ export declare function applyTaskLocalInvocationTrust(sources: readonly RecipeLibrarySource[], invocationTrust: RecipeSourceProvenance['trust']): RecipeLibrarySource[];
48
+ /**
49
+ * Load ordered library sources into one recipe index. The first source wins;
50
+ * within one source an exact adapter variant wins over the generic recipe.
51
+ */
52
+ export declare function loadRecipeLibraries(sources: readonly RecipeLibrarySource[], options?: RecipeLibraryLoadOptions): Promise<RecipeLibraryResolution>;
53
+ export interface LibraryRecipeMatch {
54
+ recipe: ResolvedLibraryRecipe;
55
+ /**
56
+ * `ref`: the precedence winner; `alias`: a qualified platform alias such as `web.greet`;
57
+ * `id`: a namespaced `<library>.<ref>` that selects that library's recipe even when shadowed.
58
+ */
59
+ resolvedBy: 'ref' | 'alias' | 'id';
60
+ }
61
+ /**
62
+ * Resolve a recipe name the way `run` and discovery both do: a ref or alias in the resolution
63
+ * first, then a namespaced `<library>.<ref>` id against that one library.
64
+ */
65
+ export declare function findLibraryRecipe(name: string, sources: readonly RecipeLibrarySource[], resolution: RecipeLibraryResolution, options?: RecipeLibraryLoadOptions & {
66
+ /** Loader for a single library, so callers can reuse loads they already made. */
67
+ load?: (source: RecipeLibrarySource) => Promise<RecipeLibraryResolution>;
68
+ }): Promise<LibraryRecipeMatch | undefined>;
69
+ /** Recipe files under recipes/, relative to it, from the shared library walker. */
70
+ export declare function listRecipeFiles(root: string): Promise<string[]>;
71
+ /**
72
+ * Log the resolved sources and shadowed recipes. With `refs`, only shadows among those refs are
73
+ * reported, so a run does not warn about a winner it never executes.
74
+ */
75
+ export declare function logRecipeLibraryResolution(logger: RecipeLogger, resolution: RecipeLibraryResolution, refs?: ReadonlySet<string>): void;
76
+ /** The `Recipe libraries: …` line; log it as soon as libraries load, before anything can fail. */
77
+ export declare function logRecipeLibrarySources(logger: RecipeLogger, resolution: RecipeLibraryResolution): void;
78
+ /** Shadow warnings, limited to `refs` when given. */
79
+ export declare function logRecipeLibraryShadows(logger: RecipeLogger, resolution: RecipeLibraryResolution, refs?: ReadonlySet<string>): void;
80
+ //# sourceMappingURL=library.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"library.d.ts","sourceRoot":"","sources":["../../src/core/library.ts"],"names":[],"mappings":"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"}