@relayflows/sdk 2.0.24 → 2.0.26

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 (167) hide show
  1. package/dist/authored-flow-executor.d.ts +8 -0
  2. package/dist/authored-flow-executor.d.ts.map +1 -1
  3. package/dist/authored-flow-executor.js +40 -1
  4. package/dist/authored-flow-executor.js.map +1 -1
  5. package/dist/authored-flow-loader.d.ts +15 -2
  6. package/dist/authored-flow-loader.d.ts.map +1 -1
  7. package/dist/authored-flow-loader.js +33 -4
  8. package/dist/authored-flow-loader.js.map +1 -1
  9. package/dist/authored-hooks.d.ts +29 -0
  10. package/dist/authored-hooks.d.ts.map +1 -0
  11. package/dist/authored-hooks.js +117 -0
  12. package/dist/authored-hooks.js.map +1 -0
  13. package/dist/authored-node-entry.js +1 -0
  14. package/dist/authored-node-entry.js.map +1 -1
  15. package/dist/authored-root.d.ts +7 -0
  16. package/dist/authored-root.d.ts.map +1 -1
  17. package/dist/authored-root.js +14 -1
  18. package/dist/authored-root.js.map +1 -1
  19. package/dist/authored-source-authority.d.ts.map +1 -1
  20. package/dist/authored-source-authority.js +6 -0
  21. package/dist/authored-source-authority.js.map +1 -1
  22. package/dist/babysitter-catalog-export.d.ts +9 -0
  23. package/dist/babysitter-catalog-export.d.ts.map +1 -0
  24. package/dist/babysitter-catalog-export.js +36 -0
  25. package/dist/babysitter-catalog-export.js.map +1 -0
  26. package/dist/bundle-extensions.d.ts +19 -0
  27. package/dist/bundle-extensions.d.ts.map +1 -0
  28. package/dist/bundle-extensions.js +95 -0
  29. package/dist/bundle-extensions.js.map +1 -0
  30. package/dist/bundle-typescript.js +1 -1
  31. package/dist/bundle-typescript.js.map +1 -1
  32. package/dist/bundle.d.ts +11 -0
  33. package/dist/bundle.d.ts.map +1 -1
  34. package/dist/bundle.js +13 -4
  35. package/dist/bundle.js.map +1 -1
  36. package/dist/cli/add-extension.d.ts +28 -0
  37. package/dist/cli/add-extension.d.ts.map +1 -0
  38. package/dist/cli/add-extension.js +135 -0
  39. package/dist/cli/add-extension.js.map +1 -0
  40. package/dist/cli/add.d.ts +3 -0
  41. package/dist/cli/add.d.ts.map +1 -1
  42. package/dist/cli/add.js +8 -0
  43. package/dist/cli/add.js.map +1 -1
  44. package/dist/cli/build.d.ts.map +1 -1
  45. package/dist/cli/build.js +7 -2
  46. package/dist/cli/build.js.map +1 -1
  47. package/dist/cli/check-helper-body.d.ts.map +1 -1
  48. package/dist/cli/check-helper-body.js +4 -0
  49. package/dist/cli/check-helper-body.js.map +1 -1
  50. package/dist/cli/check-triggers.d.ts.map +1 -1
  51. package/dist/cli/check-triggers.js +25 -2
  52. package/dist/cli/check-triggers.js.map +1 -1
  53. package/dist/cli/check-typescript.d.ts.map +1 -1
  54. package/dist/cli/check-typescript.js +5 -1
  55. package/dist/cli/check-typescript.js.map +1 -1
  56. package/dist/cli/check.d.ts +24 -0
  57. package/dist/cli/check.d.ts.map +1 -1
  58. package/dist/cli/check.js.map +1 -1
  59. package/dist/cli/cloud-deploy.d.ts +1 -0
  60. package/dist/cli/cloud-deploy.d.ts.map +1 -1
  61. package/dist/cli/cloud-deploy.js +11 -1
  62. package/dist/cli/cloud-deploy.js.map +1 -1
  63. package/dist/cli/plugin.d.ts +50 -0
  64. package/dist/cli/plugin.d.ts.map +1 -0
  65. package/dist/cli/plugin.js +295 -0
  66. package/dist/cli/plugin.js.map +1 -0
  67. package/dist/cli-commands.d.ts +46 -3
  68. package/dist/cli-commands.d.ts.map +1 -1
  69. package/dist/cli-commands.js +32 -2
  70. package/dist/cli-commands.js.map +1 -1
  71. package/dist/cli.d.ts +2 -1
  72. package/dist/cli.d.ts.map +1 -1
  73. package/dist/cli.js +26 -1
  74. package/dist/cli.js.map +1 -1
  75. package/dist/cloud-deploy.d.ts +5 -0
  76. package/dist/cloud-deploy.d.ts.map +1 -1
  77. package/dist/cloud-deploy.js +7 -6
  78. package/dist/cloud-deploy.js.map +1 -1
  79. package/dist/cloud-run.d.ts +2 -0
  80. package/dist/cloud-run.d.ts.map +1 -1
  81. package/dist/cloud-run.js +14 -4
  82. package/dist/cloud-run.js.map +1 -1
  83. package/dist/failure-kinds.d.ts +2 -2
  84. package/dist/failure-kinds.d.ts.map +1 -1
  85. package/dist/flow-extension-compat.d.ts +18 -0
  86. package/dist/flow-extension-compat.d.ts.map +1 -0
  87. package/dist/flow-extension-compat.js +33 -0
  88. package/dist/flow-extension-compat.js.map +1 -0
  89. package/dist/flow-extension-loader.d.ts +92 -0
  90. package/dist/flow-extension-loader.d.ts.map +1 -0
  91. package/dist/flow-extension-loader.js +241 -0
  92. package/dist/flow-extension-loader.js.map +1 -0
  93. package/dist/flow-extension-manifest.d.ts +61 -0
  94. package/dist/flow-extension-manifest.d.ts.map +1 -0
  95. package/dist/flow-extension-manifest.js +188 -0
  96. package/dist/flow-extension-manifest.js.map +1 -0
  97. package/dist/flow-extension-submit.d.ts +38 -0
  98. package/dist/flow-extension-submit.d.ts.map +1 -0
  99. package/dist/flow-extension-submit.js +94 -0
  100. package/dist/flow-extension-submit.js.map +1 -0
  101. package/dist/flow-requirements.d.ts +13 -3
  102. package/dist/flow-requirements.d.ts.map +1 -1
  103. package/dist/flow-requirements.js +30 -0
  104. package/dist/flow-requirements.js.map +1 -1
  105. package/dist/plugin-github.d.ts +35 -0
  106. package/dist/plugin-github.d.ts.map +1 -0
  107. package/dist/plugin-github.js +92 -0
  108. package/dist/plugin-github.js.map +1 -0
  109. package/dist/plugin-loader.d.ts.map +1 -1
  110. package/dist/plugin-loader.js +5 -1
  111. package/dist/plugin-loader.js.map +1 -1
  112. package/dist/plugin-lock.d.ts +79 -0
  113. package/dist/plugin-lock.d.ts.map +1 -0
  114. package/dist/plugin-lock.js +203 -0
  115. package/dist/plugin-lock.js.map +1 -0
  116. package/dist/plugin-manifest.d.ts +5 -1
  117. package/dist/plugin-manifest.d.ts.map +1 -1
  118. package/dist/plugin-manifest.js +18 -0
  119. package/dist/plugin-manifest.js.map +1 -1
  120. package/dist/plugin-source.d.ts +38 -0
  121. package/dist/plugin-source.d.ts.map +1 -0
  122. package/dist/plugin-source.js +84 -0
  123. package/dist/plugin-source.js.map +1 -0
  124. package/dist/plugin-store.d.ts +30 -0
  125. package/dist/plugin-store.d.ts.map +1 -0
  126. package/dist/plugin-store.js +133 -0
  127. package/dist/plugin-store.js.map +1 -0
  128. package/dist/semver-range.d.ts +10 -0
  129. package/dist/semver-range.d.ts.map +1 -0
  130. package/dist/semver-range.js +88 -0
  131. package/dist/semver-range.js.map +1 -0
  132. package/package.json +9 -4
  133. package/src/authored-flow-executor.ts +60 -1
  134. package/src/authored-flow-loader.ts +49 -6
  135. package/src/authored-hooks.ts +136 -0
  136. package/src/authored-node-entry.ts +1 -0
  137. package/src/authored-root.ts +22 -1
  138. package/src/authored-source-authority.ts +6 -0
  139. package/src/babysitter-catalog-export.ts +44 -0
  140. package/src/bundle-extensions.ts +85 -0
  141. package/src/bundle-typescript.ts +1 -1
  142. package/src/bundle.ts +14 -4
  143. package/src/cli/add-extension.ts +134 -0
  144. package/src/cli/add.ts +9 -0
  145. package/src/cli/build.ts +7 -2
  146. package/src/cli/check-helper-body.ts +4 -0
  147. package/src/cli/check-triggers.ts +27 -2
  148. package/src/cli/check-typescript.ts +5 -1
  149. package/src/cli/check.ts +23 -0
  150. package/src/cli/cloud-deploy.ts +11 -1
  151. package/src/cli/plugin.ts +286 -0
  152. package/src/cli-commands.ts +32 -2
  153. package/src/cli.ts +23 -1
  154. package/src/cloud-deploy.ts +18 -8
  155. package/src/cloud-run.ts +16 -6
  156. package/src/flow-extension-compat.ts +35 -0
  157. package/src/flow-extension-loader.ts +304 -0
  158. package/src/flow-extension-manifest.ts +190 -0
  159. package/src/flow-extension-submit.ts +120 -0
  160. package/src/flow-requirements.ts +44 -3
  161. package/src/plugin-github.ts +98 -0
  162. package/src/plugin-loader.ts +5 -1
  163. package/src/plugin-lock.ts +194 -0
  164. package/src/plugin-manifest.ts +18 -0
  165. package/src/plugin-source.ts +91 -0
  166. package/src/plugin-store.ts +105 -0
  167. package/src/semver-range.ts +74 -0
@@ -0,0 +1,105 @@
1
+ import { lstat, mkdir, mkdtemp, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises';
2
+ import { dirname, join, resolve } from 'node:path';
3
+ import { payloadManifest, safePath, sha256 } from './bundle.js';
4
+ import { PluginError } from './plugin-manifest.js';
5
+
6
+ /**
7
+ * Where a flow-extension plugin's bytes live inside a project:
8
+ * `<project>/.flows/plugins/<name>@sha256:<digest>/`, content-addressed like
9
+ * the bundle cache and never `node_modules`. `manifest.json` in that directory
10
+ * is the payload manifest whose sha256 is the digest, so a directory can be
11
+ * re-verified without the network — and, because the lockfile records the
12
+ * same digest, drift between what was installed and what is on disk is a
13
+ * refusal, not a surprise.
14
+ */
15
+ export const PLUGIN_STORE = '.flows/plugins';
16
+
17
+ export function pluginStoreDirectory(root: string, name: string, digest: string): string {
18
+ return join(resolve(root), PLUGIN_STORE, `${name}@sha256:${digest}`);
19
+ }
20
+
21
+ export interface StoredPluginFile { readonly path: string; readonly data: Uint8Array }
22
+
23
+ /** Write the files atomically; an existing directory is verified instead of overwritten. */
24
+ export async function materializePlugin(root: string, name: string, files: readonly StoredPluginFile[]): Promise<{ directory: string; digest: string }> {
25
+ const manifest = payloadManifest(files);
26
+ const digest = sha256(manifest);
27
+ const directory = pluginStoreDirectory(root, name, digest);
28
+ let exists = false;
29
+ try { await lstat(directory); exists = true; }
30
+ catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; }
31
+ if (exists) { await verifyStoredPlugin(directory, digest); return { directory, digest }; }
32
+ const parent = dirname(directory);
33
+ await mkdir(parent, { recursive: true });
34
+ const staging = await mkdtemp(join(parent, '.install-'));
35
+ try {
36
+ for (const file of files) {
37
+ if (!safePath(file.path) || file.path === 'manifest.json') throw new PluginError('plugin_path_invalid', `${file.path}: invalid plugin path.`);
38
+ await mkdir(dirname(join(staging, file.path)), { recursive: true });
39
+ await writeFile(join(staging, file.path), file.data, { mode: 0o644 });
40
+ }
41
+ await writeFile(join(staging, 'manifest.json'), manifest);
42
+ try { await rename(staging, directory); }
43
+ catch (error) {
44
+ if (!['EEXIST', 'ENOTEMPTY'].includes((error as NodeJS.ErrnoException).code ?? '')) throw error;
45
+ await verifyStoredPlugin(directory, digest);
46
+ }
47
+ } finally { await rm(staging, { recursive: true, force: true }); }
48
+ return { directory, digest };
49
+ }
50
+
51
+ async function regularFile(root: string, path: string): Promise<Buffer> {
52
+ const parts = path.split('/');
53
+ for (let i = 1; i <= parts.length; i++) {
54
+ const stat = await lstat(join(root, ...parts.slice(0, i)));
55
+ if (i === parts.length ? !stat.isFile() : !stat.isDirectory()) throw new PluginError('plugin_source_drift', `${path}: expected a regular file, without symlinks.`);
56
+ }
57
+ return readFile(join(root, path));
58
+ }
59
+
60
+ /** Re-hash a materialized plugin and compare with the digest the lockfile recorded. */
61
+ export async function verifyStoredPlugin(directory: string, expectedDigest: string): Promise<void> {
62
+ const drift = (message: string): never => { throw new PluginError('plugin_source_drift', `${directory}: ${message}`); };
63
+ let raw: string;
64
+ try {
65
+ if (!(await lstat(directory)).isDirectory()) return drift('not a directory');
66
+ raw = (await regularFile(directory, 'manifest.json')).toString('utf8');
67
+ } catch (error) { return drift(error instanceof PluginError ? error.message : 'manifest.json is missing'); }
68
+ if (sha256(raw) !== expectedDigest) return drift('manifest.json digest differs from the lockfile');
69
+ let entries: { path: string; sha256: string; bytes: number }[];
70
+ try { entries = JSON.parse(raw); if (!Array.isArray(entries)) throw new Error(); }
71
+ catch { return drift('manifest.json is not a manifest'); }
72
+ const paths = new Set<string>();
73
+ for (const entry of entries) {
74
+ if (typeof entry?.path !== 'string' || !safePath(entry.path)) return drift('manifest.json lists an invalid path');
75
+ let data: Buffer;
76
+ try { data = await regularFile(directory, entry.path); }
77
+ catch (error) { return drift(error instanceof PluginError ? error.message : `${entry.path} is missing`); }
78
+ if (data.length !== entry.bytes || sha256(data) !== entry.sha256) return drift(`${entry.path} changed since installation`);
79
+ paths.add(entry.path);
80
+ }
81
+ await rejectExtras(directory, '', new Set([...paths, 'manifest.json']), drift);
82
+ }
83
+
84
+ /** Re-verify, then return every stored file including the payload `manifest.json`. */
85
+ export async function readStoredPluginFiles(directory: string, expectedDigest: string): Promise<readonly { path: string; data: Buffer }[]> {
86
+ await verifyStoredPlugin(directory, expectedDigest);
87
+ const manifest = await regularFile(directory, 'manifest.json');
88
+ const entries = JSON.parse(manifest.toString('utf8')) as { path: string }[];
89
+ const files = [{ path: 'manifest.json', data: manifest }];
90
+ for (const entry of entries) files.push({ path: entry.path, data: await regularFile(directory, entry.path) });
91
+ return files;
92
+ }
93
+
94
+ /** Drop a materialized plugin directory. Missing is a no-op. */
95
+ export async function removeStoredPlugin(directory: string): Promise<void> {
96
+ await rm(directory, { recursive: true, force: true });
97
+ }
98
+
99
+ async function rejectExtras(root: string, prefix: string, paths: Set<string>, drift: (m: string) => never): Promise<void> {
100
+ for (const entry of await readdir(join(root, prefix), { withFileTypes: true })) {
101
+ const path = prefix + entry.name;
102
+ if (entry.isDirectory() && [...paths].some(file => file.startsWith(`${path}/`))) await rejectExtras(root, `${path}/`, paths, drift);
103
+ else if (!entry.isFile() || !paths.has(path)) drift(`${path}: unlisted file or unsupported file type`);
104
+ }
105
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * The small semver subset plugin `compat` ranges may use. Deliberately not a
3
+ * dependency on `semver`: a plugin's compatibility claim has to be checkable by
4
+ * every reader — Cloud, the CLI, a catalog — from one short, obvious rule.
5
+ *
6
+ * Accepted: `*`, `x.y.z`, `^x.y.z`, `~x.y.z`, `>=x.y.z`, `>=x.y.z <a.b.c`.
7
+ * Prerelease tags are ordered lexically after the numeric triple and only
8
+ * compare against a range whose bounds name the same triple.
9
+ */
10
+ const VERSION = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/;
11
+ const RANGE = /^(\*|(?:[\^~]|>=)?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?: <\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)?)$/;
12
+
13
+ interface Parsed { readonly triple: readonly [number, number, number]; readonly pre?: string }
14
+
15
+ export function parseVersion(value: string): Parsed | undefined {
16
+ const m = VERSION.exec(value);
17
+ if (!m) return undefined;
18
+ const triple = [Number(m[1]), Number(m[2]), Number(m[3])] as const;
19
+ if (triple.some(n => !Number.isSafeInteger(n))) return undefined;
20
+ return m[4] === undefined ? { triple } : { triple, pre: m[4] };
21
+ }
22
+
23
+ export function isVersionRange(value: string): boolean {
24
+ return RANGE.test(value);
25
+ }
26
+
27
+ function compare(a: Parsed, b: Parsed): number {
28
+ for (let i = 0; i < 3; i++) if (a.triple[i] !== b.triple[i]) return a.triple[i]! - b.triple[i]!;
29
+ if (a.pre === b.pre) return 0;
30
+ if (a.pre === undefined) return 1;
31
+ if (b.pre === undefined) return -1;
32
+ return comparePrerelease(a.pre, b.pre);
33
+ }
34
+
35
+ function comparePrerelease(a: string, b: string): number {
36
+ const as = a.split('.');
37
+ const bs = b.split('.');
38
+ const n = Math.max(as.length, bs.length);
39
+ for (let i = 0; i < n; i++) {
40
+ const left = as[i];
41
+ const right = bs[i];
42
+ if (left === undefined) return -1;
43
+ if (right === undefined) return 1;
44
+ const leftNum = /^\d+$/.test(left) ? Number(left) : undefined;
45
+ const rightNum = /^\d+$/.test(right) ? Number(right) : undefined;
46
+ if (leftNum !== undefined && rightNum !== undefined) {
47
+ if (leftNum !== rightNum) return leftNum - rightNum;
48
+ continue;
49
+ }
50
+ if (leftNum !== undefined) return -1;
51
+ if (rightNum !== undefined) return 1;
52
+ if (left !== right) return left < right ? -1 : 1;
53
+ }
54
+ return 0;
55
+ }
56
+
57
+ /** Whether `version` satisfies `range`; false for malformed input rather than a throw. */
58
+ export function satisfiesRange(version: string, range: string): boolean {
59
+ const v = parseVersion(version);
60
+ if (v === undefined || !isVersionRange(range)) return false;
61
+ if (range === '*') return true;
62
+ const [lowerText, upperText] = range.split(' ') as [string, string | undefined];
63
+ const operator = /^[\^~]|^>=/.exec(lowerText)?.[0] ?? '';
64
+ const lower = parseVersion(lowerText.slice(operator.length))!;
65
+ if (compare(v, lower) < 0) return false;
66
+ if (operator === '') return compare(v, lower) === 0;
67
+ let upper: Parsed | undefined;
68
+ if (upperText !== undefined) upper = parseVersion(upperText.slice(1));
69
+ else if (operator === '^') {
70
+ const [major, minor] = lower.triple;
71
+ upper = major > 0 ? { triple: [major + 1, 0, 0] } : minor > 0 ? { triple: [0, minor + 1, 0] } : { triple: [0, 0, lower.triple[2] + 1] };
72
+ } else if (operator === '~') upper = { triple: [lower.triple[0], lower.triple[1] + 1, 0] };
73
+ return upper === undefined || compare(v, upper) < 0;
74
+ }