@peers-app/peers-sdk 0.26.6 → 0.27.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 (189) hide show
  1. package/README.md +3 -1
  2. package/bin/peers-package.js +13 -0
  3. package/define/package.json +4 -0
  4. package/dist/contracts/__tests__/action-dispatch.test.js +56 -76
  5. package/dist/contracts/__tests__/also-implements.test.d.ts +1 -0
  6. package/dist/contracts/__tests__/also-implements.test.js +110 -0
  7. package/dist/contracts/__tests__/builder.test.js +45 -0
  8. package/dist/contracts/__tests__/contract-resolvers.test.js +134 -4
  9. package/dist/contracts/__tests__/frozen-contracts.test.d.ts +1 -0
  10. package/dist/contracts/__tests__/frozen-contracts.test.js +191 -0
  11. package/dist/contracts/__tests__/generic-tool-dispatch.test.js +13 -56
  12. package/dist/contracts/__tests__/host-contract-client.test.d.ts +1 -0
  13. package/dist/contracts/__tests__/host-contract-client.test.js +793 -0
  14. package/dist/contracts/__tests__/host-contract-transport.test.js +15 -0
  15. package/dist/contracts/__tests__/registry.test.js +68 -0
  16. package/dist/contracts/__tests__/target-dispatch.d.ts +11 -0
  17. package/dist/contracts/__tests__/target-dispatch.js +24 -0
  18. package/dist/contracts/__tests__/tool-catalog-service.test.js +37 -1
  19. package/dist/contracts/__tests__/tool-catalog.test.js +12 -3
  20. package/dist/contracts/__tests__/validate.test.js +177 -7
  21. package/dist/contracts/__tests__/workflow-context.test.js +0 -11
  22. package/dist/contracts/builder.d.ts +16 -2
  23. package/dist/contracts/builder.js +44 -3
  24. package/dist/contracts/contract-definition-schema.d.ts +251 -0
  25. package/dist/contracts/contract-definition-schema.js +48 -0
  26. package/dist/contracts/contract-provider-router.d.ts +2 -0
  27. package/dist/contracts/contract-provider-router.js +5 -2
  28. package/dist/contracts/contract-proxy.d.ts +52 -18
  29. package/dist/contracts/contract-proxy.js +144 -203
  30. package/dist/contracts/contract-resolvers.d.ts +6 -4
  31. package/dist/contracts/contract-resolvers.js +32 -9
  32. package/dist/contracts/frozen-contracts.d.ts +59 -0
  33. package/dist/contracts/frozen-contracts.js +86 -0
  34. package/dist/contracts/host-contract-client.d.ts +134 -0
  35. package/dist/contracts/host-contract-client.js +396 -0
  36. package/dist/contracts/host-contract-transport.d.ts +2 -0
  37. package/dist/contracts/host-contract-transport.js +8 -0
  38. package/dist/contracts/index.d.ts +6 -4
  39. package/dist/contracts/index.js +11 -5
  40. package/dist/contracts/registry.d.ts +31 -1
  41. package/dist/contracts/registry.js +100 -6
  42. package/dist/contracts/system/contract-catalog-contract.d.ts +61 -6
  43. package/dist/contracts/system/contract-catalog-contract.js +17 -5
  44. package/dist/contracts/tool-catalog-service.js +26 -6
  45. package/dist/contracts/tool-catalog.d.ts +1 -1
  46. package/dist/contracts/tool-catalog.js +4 -1
  47. package/dist/contracts/types.d.ts +32 -2
  48. package/dist/contracts/validate.d.ts +16 -1
  49. package/dist/contracts/validate.js +139 -25
  50. package/dist/data/change-tracking.d.ts +17 -5
  51. package/dist/data/change-tracking.js +22 -6
  52. package/dist/data/change-tracking.test.d.ts +1 -0
  53. package/dist/data/change-tracking.test.js +62 -0
  54. package/dist/data/create-group.js +1 -1
  55. package/dist/data/group-members.d.ts +14 -0
  56. package/dist/data/group-members.js +36 -0
  57. package/dist/data/group-members.test.js +49 -0
  58. package/dist/data/orm/sql.data-source.d.ts +9 -0
  59. package/dist/data/orm/sql.data-source.js +47 -2
  60. package/dist/data/orm/sql.data-source.test.js +142 -0
  61. package/dist/data/orm/table-container.js +9 -9
  62. package/dist/data/orm/table-container.test.js +44 -0
  63. package/dist/data/orm/types.d.ts +41 -1
  64. package/dist/data/orm/types.js +159 -28
  65. package/dist/data/orm/types.test.js +129 -0
  66. package/dist/data/peer-types.d.ts +8 -62
  67. package/dist/data/persistent-vars.d.ts +4 -1
  68. package/dist/data/persistent-vars.js +7 -2
  69. package/dist/data/tools.d.ts +29 -221
  70. package/dist/define/also-implements.test.d.ts +1 -0
  71. package/dist/define/also-implements.test.js +123 -0
  72. package/dist/define/context.d.ts +77 -0
  73. package/dist/define/context.js +2 -0
  74. package/dist/define/contract-ref.d.ts +177 -0
  75. package/dist/define/contract-ref.js +67 -0
  76. package/dist/define/declare-contract.d.ts +167 -0
  77. package/dist/define/declare-contract.js +220 -0
  78. package/dist/define/declare-contract.test.d.ts +1 -0
  79. package/dist/define/declare-contract.test.js +573 -0
  80. package/dist/define/define-package.d.ts +203 -0
  81. package/dist/define/define-package.js +289 -0
  82. package/dist/define/define.test.d.ts +1 -0
  83. package/dist/define/define.test.js +1010 -0
  84. package/dist/define/empty-fields.d.ts +15 -0
  85. package/dist/define/empty-fields.js +134 -0
  86. package/dist/define/empty-fields.test.d.ts +1 -0
  87. package/dist/define/empty-fields.test.js +55 -0
  88. package/dist/define/events-observables.test.d.ts +1 -0
  89. package/dist/define/events-observables.test.js +1013 -0
  90. package/dist/define/events.d.ts +81 -0
  91. package/dist/define/events.js +81 -0
  92. package/dist/define/generated-field.d.ts +20 -0
  93. package/dist/define/generated-field.js +25 -0
  94. package/dist/define/index.d.ts +28 -0
  95. package/dist/define/index.js +62 -0
  96. package/dist/define/lazy-members.d.ts +8 -0
  97. package/dist/define/lazy-members.js +38 -0
  98. package/dist/define/manifest.d.ts +63 -0
  99. package/dist/define/manifest.js +653 -0
  100. package/dist/define/observables.d.ts +146 -0
  101. package/dist/define/observables.js +187 -0
  102. package/dist/define/serve-members.d.ts +21 -0
  103. package/dist/define/serve-members.js +152 -0
  104. package/dist/define/serve-members.test.d.ts +1 -0
  105. package/dist/define/serve-members.test.js +630 -0
  106. package/dist/define/serve.d.ts +22 -0
  107. package/dist/define/serve.js +58 -0
  108. package/dist/define/system-contracts.d.ts +41 -0
  109. package/dist/define/system-contracts.js +21 -0
  110. package/dist/define/table-handle.d.ts +13 -0
  111. package/dist/define/table-handle.js +36 -0
  112. package/dist/define/table.d.ts +128 -0
  113. package/dist/define/table.js +122 -0
  114. package/dist/define/tool.d.ts +129 -0
  115. package/dist/define/tool.js +91 -0
  116. package/dist/define/vars.d.ts +82 -0
  117. package/dist/define/vars.js +256 -0
  118. package/dist/define/vars.test.d.ts +1 -0
  119. package/dist/define/vars.test.js +39 -0
  120. package/dist/define/wire-value.d.ts +12 -0
  121. package/dist/define/wire-value.js +76 -0
  122. package/dist/define/worker-state.d.ts +15 -0
  123. package/dist/define/worker-state.js +45 -0
  124. package/dist/device/tx-encoding.test.js +10 -0
  125. package/dist/index.d.ts +3 -0
  126. package/dist/index.js +5 -1
  127. package/dist/invites/group-approval.js +4 -2
  128. package/dist/invites/group-approval.test.js +2 -0
  129. package/dist/observable.d.ts +42 -2
  130. package/dist/observable.js +25 -7
  131. package/dist/package-build/build-package.d.ts +60 -0
  132. package/dist/package-build/build-package.js +312 -0
  133. package/dist/package-build/build-package.test.d.ts +1 -0
  134. package/dist/package-build/build-package.test.js +204 -0
  135. package/dist/package-build/cli.d.ts +7 -0
  136. package/dist/package-build/cli.js +218 -0
  137. package/dist/package-build/contract-add.d.ts +35 -0
  138. package/dist/package-build/contract-add.js +97 -0
  139. package/dist/package-build/contract-add.test.d.ts +1 -0
  140. package/dist/package-build/contract-add.test.js +467 -0
  141. package/dist/package-build/contract-source.d.ts +59 -0
  142. package/dist/package-build/contract-source.js +471 -0
  143. package/dist/package-build/freeze-contracts.d.ts +56 -0
  144. package/dist/package-build/freeze-contracts.js +198 -0
  145. package/dist/package-build/freeze-contracts.test.d.ts +1 -0
  146. package/dist/package-build/freeze-contracts.test.js +355 -0
  147. package/dist/package-installer/package-creator.d.ts +4 -0
  148. package/dist/package-installer/package-creator.js +57 -16
  149. package/dist/package-installer/package-creator.test.js +86 -0
  150. package/dist/package-loader/contract-members.d.ts +53 -0
  151. package/dist/package-loader/contract-members.js +31 -0
  152. package/dist/package-loader/contract-package-loader.js +5 -1
  153. package/dist/package-loader/contract-package-loader.test.js +11 -0
  154. package/dist/package-loader/host-authority-loading.test.js +9 -5
  155. package/dist/package-loader/index.d.ts +1 -0
  156. package/dist/package-loader/index.js +1 -0
  157. package/dist/package-loader/isolated-artifact.d.ts +80 -17
  158. package/dist/package-loader/isolated-artifact.js +257 -51
  159. package/dist/package-loader/isolated-artifact.test.js +294 -21
  160. package/dist/package-loader/isolated-runtime.d.ts +22 -7
  161. package/dist/package-loader/isolated-table-gateway.js +30 -11
  162. package/dist/package-loader/isolated-table-gateway.test.js +243 -0
  163. package/dist/package-loader/package-loader.d.ts +8 -1
  164. package/dist/package-loader/package-loader.js +39 -20
  165. package/dist/package-loader/package-loader.test.js +47 -0
  166. package/dist/peer-id.d.ts +25 -0
  167. package/dist/peer-id.js +99 -0
  168. package/dist/serial-json.compat.test.d.ts +1 -0
  169. package/dist/serial-json.compat.test.js +369 -0
  170. package/dist/serial-json.d.ts +41 -5
  171. package/dist/serial-json.js +216 -157
  172. package/dist/serial-json.oracle.d.ts +21 -0
  173. package/dist/serial-json.oracle.js +222 -0
  174. package/dist/serial-json.test.js +315 -0
  175. package/dist/testing/index.d.ts +4 -0
  176. package/dist/testing/index.js +8 -0
  177. package/dist/testing/mock-host-contract.d.ts +123 -0
  178. package/dist/testing/mock-host-contract.js +434 -0
  179. package/dist/testing/mock-host-contract.test.d.ts +1 -0
  180. package/dist/testing/mock-host-contract.test.js +782 -0
  181. package/dist/types/field-type.d.ts +35 -18
  182. package/dist/types/field-type.js +4 -1
  183. package/dist/types/zod-types.d.ts +9 -0
  184. package/dist/types/zod-types.js +8 -2
  185. package/dist/utils.d.ts +22 -25
  186. package/dist/utils.js +45 -92
  187. package/dist/utils.test.js +36 -0
  188. package/package.json +9 -2
  189. package/testing/package.json +4 -0
@@ -0,0 +1,204 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const node_child_process_1 = require("node:child_process");
4
+ const node_crypto_1 = require("node:crypto");
5
+ const promises_1 = require("node:fs/promises");
6
+ const node_os_1 = require("node:os");
7
+ const path = require("node:path");
8
+ const node_vm_1 = require("node:vm");
9
+ const isolated_artifact_1 = require("../package-loader/isolated-artifact");
10
+ const peer_id_1 = require("../peer-id");
11
+ const build_package_1 = require("./build-package");
12
+ const sdkRoot = path.resolve(__dirname, "../..");
13
+ const packageId = (0, peer_id_1.newid)();
14
+ const contractId = (0, peer_id_1.newid)();
15
+ let root;
16
+ let watcher;
17
+ function declaration(mode = "sandbox", version = "1.0.0", bad = false) {
18
+ return `
19
+ import { defineContract, definePackage, defineTool, z } from "@peers-app/peers-sdk/define";
20
+ import { side } from "platform-probe";
21
+ // <!-- Comments that SES rejects must be stripped from the provider.
22
+ export default definePackage((pkg) => {
23
+ pkg.packageId = ${JSON.stringify(packageId)};
24
+ pkg.version = ${JSON.stringify(version)};
25
+ pkg.executionMode = ${JSON.stringify(mode)};
26
+ pkg.provides(defineContract({
27
+ contractId: ${JSON.stringify(contractId)}, version: 1, name: "Probe",
28
+ tools: [defineTool({
29
+ name: "probe", usageDescription: "Probe the bundler platform.", accessLevel: 0,
30
+ inputSchema: z.object({}), outputSchema: z.object({ side: z.string() }),
31
+ handler: () => ({ side: ${bad ? '"<!--"' : "side"} }),
32
+ })],
33
+ }));
34
+ });
35
+ `;
36
+ }
37
+ beforeEach(async () => {
38
+ root = await (0, promises_1.mkdtemp)(path.join((0, node_os_1.tmpdir)(), "peers-package-test-"));
39
+ await (0, promises_1.mkdir)(path.join(root, "src"));
40
+ await (0, promises_1.mkdir)(path.join(root, "node_modules/@peers-app"), { recursive: true });
41
+ await (0, promises_1.symlink)(sdkRoot, path.join(root, "node_modules/@peers-app/peers-sdk"), "dir");
42
+ await (0, promises_1.symlink)(path.dirname(require.resolve("esbuild/package.json")), path.join(root, "node_modules/esbuild"), "dir");
43
+ const probe = path.join(root, "node_modules/platform-probe");
44
+ await (0, promises_1.mkdir)(probe);
45
+ await (0, promises_1.writeFile)(path.join(root, "package.json"), '{"name":"build-probe","version":"1.0.0"}');
46
+ await (0, promises_1.writeFile)(path.join(probe, "package.json"), JSON.stringify({
47
+ name: "platform-probe",
48
+ exports: { browser: "./browser.js", default: "./node.js" },
49
+ }));
50
+ await (0, promises_1.writeFile)(path.join(probe, "browser.js"), 'exports.side = "browser";');
51
+ await (0, promises_1.writeFile)(path.join(probe, "node.js"), 'exports.side = "node";');
52
+ await (0, promises_1.writeFile)(path.join(root, "src/package.ts"), declaration());
53
+ });
54
+ afterEach(async () => {
55
+ await watcher?.dispose();
56
+ watcher = undefined;
57
+ await (0, promises_1.rm)(root, { recursive: true, force: true });
58
+ });
59
+ async function probeArtifact() {
60
+ const artifact = (0, isolated_artifact_1.parseIsolatedPackageArtifact)(await (0, promises_1.readFile)(path.join(root, "dist/package.bundle.js"), "utf8"));
61
+ const handlers = [];
62
+ const sandbox = {
63
+ registerContractTool: (_id, _version, _name, handler) => handlers.push(handler),
64
+ registerHandler: () => undefined,
65
+ onWorkerStart: () => undefined,
66
+ };
67
+ try {
68
+ (0, node_vm_1.runInNewContext)(artifact.providerSource, { ...sandbox, self: { crypto: node_crypto_1.webcrypto }, require });
69
+ }
70
+ catch (error) {
71
+ throw new Error(String(error));
72
+ }
73
+ return { artifact, result: await handlers[0]({}, {}) };
74
+ }
75
+ test("the CLI generates a validated manifest with custom paths and no provider artifact", async () => {
76
+ (0, node_child_process_1.execFileSync)(process.execPath, [
77
+ path.join(sdkRoot, "bin/peers-package.js"),
78
+ "manifest",
79
+ "--root",
80
+ root,
81
+ "--out-dir",
82
+ "generated",
83
+ ], { encoding: "utf8" });
84
+ const manifest = JSON.parse(await (0, promises_1.readFile)(path.join(root, "generated/manifest.json"), "utf8"));
85
+ expect(manifest).toMatchObject({ packageId, guestApi: 3, version: "1.0.0" });
86
+ await expect((0, promises_1.readFile)(path.join(root, "generated/package.bundle.js"))).rejects.toThrow();
87
+ await (0, promises_1.writeFile)(path.join(root, "src/package.ts"), declaration("sandbox", ""));
88
+ await expect((0, build_package_1.generatePackageManifest)({ root })).rejects.toThrow(/version/);
89
+ });
90
+ test.each([
91
+ "sandbox",
92
+ "privileged",
93
+ ])("builds %s with its platform's dependency exports", async (mode) => {
94
+ await (0, promises_1.writeFile)(path.join(root, "src/package.ts"), declaration(mode));
95
+ const built = await (0, build_package_1.buildPackage)({ root });
96
+ const { artifact, result } = await probeArtifact();
97
+ expect(result).toEqual({ side: mode === "sandbox" ? "browser" : "node" });
98
+ expect(artifact.providerSource).not.toContain("<!--");
99
+ const raw = JSON.parse((await (0, promises_1.readFile)(built.artifactPath, "utf8")).split("\n")[1]);
100
+ expect(raw.manifest).toEqual(JSON.parse(await (0, promises_1.readFile)(built.manifestPath, "utf8")));
101
+ });
102
+ test("manifest and provider builds resolve dependencies with an output directory outside the package", async () => {
103
+ const outDir = await (0, promises_1.mkdtemp)(path.join((0, node_os_1.tmpdir)(), "peers-package-output-test-"));
104
+ try {
105
+ const generated = await (0, build_package_1.generatePackageManifest)({ root, outDir });
106
+ expect(generated.manifest.packageId).toBe(packageId);
107
+ expect(generated.manifestPath).toBe(path.join(outDir, "manifest.json"));
108
+ const built = await (0, build_package_1.buildPackage)({ root, outDir });
109
+ const raw = JSON.parse((await (0, promises_1.readFile)(built.artifactPath, "utf8")).split("\n")[1]);
110
+ expect(raw.manifest).toEqual(JSON.parse(await (0, promises_1.readFile)(built.manifestPath, "utf8")));
111
+ }
112
+ finally {
113
+ await (0, promises_1.rm)(outDir, { recursive: true, force: true });
114
+ }
115
+ });
116
+ test("an interrupted build leaves no scratch files in the package's source tree", async () => {
117
+ // Killed mid-evaluation, as Ctrl-C during a watch rebuild would: no cleanup code runs.
118
+ await (0, promises_1.writeFile)(path.join(root, "src/package.ts"), `process.kill(process.pid, "SIGKILL");\n${declaration()}`);
119
+ expect(() => (0, node_child_process_1.execFileSync)(process.execPath, [path.join(sdkRoot, "bin/peers-package.js"), "manifest", "--root", root], { stdio: "pipe" })).toThrow();
120
+ expect((await (0, promises_1.readdir)(root)).sort()).toEqual(["node_modules", "package.json", "src"]);
121
+ });
122
+ test("a failed provider build preserves both previously successful outputs", async () => {
123
+ const first = await (0, build_package_1.buildPackage)({ root });
124
+ const before = await Promise.all([(0, promises_1.readFile)(first.manifestPath), (0, promises_1.readFile)(first.artifactPath)]);
125
+ jest.resetModules();
126
+ await (0, promises_1.writeFile)(path.join(root, "src/package.ts"), declaration("sandbox", "2.0.0", true));
127
+ await expect((0, build_package_1.buildPackage)({ root })).rejects.toThrow(/HTML comment token/);
128
+ expect(await Promise.all([(0, promises_1.readFile)(first.manifestPath), (0, promises_1.readFile)(first.artifactPath)])).toEqual(before);
129
+ });
130
+ test("repeated builds reload external declaration dependencies, including after an evaluation failure", async () => {
131
+ const source = declaration().replace('pkg.version = "1.0.0";', "pkg.version = side;");
132
+ await (0, promises_1.writeFile)(path.join(root, "src/package.ts"), source);
133
+ const stdout = (0, node_child_process_1.execFileSync)(process.execPath, [
134
+ "-e",
135
+ `
136
+ const { writeFile } = require("node:fs/promises");
137
+ const { buildPackage } = require(${JSON.stringify(path.join(sdkRoot, "dist/package-build/build-package.js"))});
138
+ const root = ${JSON.stringify(root)};
139
+ const dependency = ${JSON.stringify(path.join(root, "node_modules/platform-probe/node.js"))};
140
+ (async () => {
141
+ const versions = [];
142
+ await writeFile(dependency, 'exports.side = "1.0.0";');
143
+ versions.push((await buildPackage({ root })).manifest.version);
144
+ await writeFile(dependency, 'exports.side = "";');
145
+ try { await buildPackage({ root }); throw new Error("Invalid declaration accepted"); }
146
+ catch (error) { if (!error.message.includes("must set version")) throw error; }
147
+ await writeFile(dependency, 'exports.side = "2.0.0";');
148
+ versions.push((await buildPackage({ root })).manifest.version);
149
+ process.stdout.write(JSON.stringify(versions));
150
+ })().catch((error) => { console.error(error); process.exitCode = 1; });
151
+ `,
152
+ ], { encoding: "utf8", timeout: 10_000 });
153
+ expect(JSON.parse(stdout)).toEqual(["1.0.0", "2.0.0"]);
154
+ });
155
+ test("watch rebuilds use the current execution mode and recover after invalid declarations", async () => {
156
+ const queued = [];
157
+ let deliver;
158
+ const next = () => new Promise((resolve, reject) => {
159
+ const pending = queued.shift();
160
+ if (pending)
161
+ return resolve(pending);
162
+ const timeout = setTimeout(() => reject(new Error("No watch build completed")), 10_000);
163
+ deliver = (value) => {
164
+ clearTimeout(timeout);
165
+ deliver = undefined;
166
+ resolve(value);
167
+ };
168
+ });
169
+ watcher = await (0, build_package_1.watchPackage)({ root }, (value) => {
170
+ if (deliver)
171
+ deliver(value);
172
+ else
173
+ queued.push(value);
174
+ });
175
+ const until = async (matches) => {
176
+ let value = await next();
177
+ while (!matches(value))
178
+ value = await next();
179
+ return value;
180
+ };
181
+ expect(await next()).not.toBeInstanceOf(Error);
182
+ for (const mode of ["privileged", "sandbox"]) {
183
+ // Jest's require cache is separate from the native cache invalidated by loadDefinition.
184
+ jest.resetModules();
185
+ await (0, promises_1.writeFile)(path.join(root, "src/package.ts"), declaration(mode));
186
+ expect(await until((value) => !(value instanceof Error) && value.manifest.executionMode === mode)).toMatchObject({ manifest: { executionMode: mode } });
187
+ expect((await probeArtifact()).result).toEqual({
188
+ side: mode === "sandbox" ? "browser" : "node",
189
+ });
190
+ }
191
+ // After changing modes, watch the active conditional dependency as well as src/package.ts.
192
+ await (0, promises_1.writeFile)(path.join(root, "node_modules/platform-probe/browser.js"), 'exports.side = "browser-v2";');
193
+ let side;
194
+ do {
195
+ expect(await next()).not.toBeInstanceOf(Error);
196
+ side = (await probeArtifact()).result.side;
197
+ } while (side !== "browser-v2");
198
+ jest.resetModules();
199
+ await (0, promises_1.writeFile)(path.join(root, "src/package.ts"), declaration("sandbox", ""));
200
+ expect(await until((value) => value instanceof Error)).toBeInstanceOf(Error);
201
+ jest.resetModules();
202
+ await (0, promises_1.writeFile)(path.join(root, "src/package.ts"), declaration("sandbox", "2.0.0"));
203
+ expect(await until((value) => !(value instanceof Error) && value.manifest.version === "2.0.0")).toMatchObject({ manifest: { version: "2.0.0" } });
204
+ }, 30_000);
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Run the `peers-package` command.
3
+ *
4
+ * @param argv - Arguments after the command name, such as `["build", "--watch"]`.
5
+ * @returns The exit code, or never resolves while watching.
6
+ */
7
+ export declare function main(argv: string[]): Promise<number>;
@@ -0,0 +1,218 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.main = main;
4
+ const node_fs_1 = require("node:fs");
5
+ const path = require("node:path");
6
+ const build_package_1 = require("./build-package");
7
+ const contract_add_1 = require("./contract-add");
8
+ const freeze_contracts_1 = require("./freeze-contracts");
9
+ const USAGE = `Usage: peers-package <command> [options]
10
+
11
+ Commands:
12
+ manifest Write dist/manifest.json from the package's declarations (src/package.ts).
13
+ build Write the manifest and dist/package.bundle.js, the artifact the Runtime
14
+ installs.
15
+ contract add <source> Write src/contracts/<name>.v<version>.ts, a declaration of a contract this
16
+ package implements or calls, generated from <source>: a declaration module
17
+ and its export (path/to/file.ts#Name), a provider's manifest.json or
18
+ package.bundle.js, or the JSON getContractDefinition returned.
19
+ contract freeze Before releasing a version as stable: record the contract versions it
20
+ provides in frozen-contracts.json, whose shapes can't change again. A
21
+ build fails if it changes one.
22
+
23
+ Options:
24
+ --watch Rebuild on every change (build only).
25
+ --root <dir> Package root. Defaults to the current directory.
26
+ --entry <file> Declaration module. Defaults to src/package.ts.
27
+ --out-dir <dir> Output directory. Defaults to dist (src/contracts for contract add).
28
+ --contract <id@v> The contract to read, when a manifest provides several (contract add).
29
+ --name <Name> The declaration's export name (contract add).
30
+ --check Write nothing; fail if the declaration is missing or out of date
31
+ (contract add), or if a frozen contract changed (contract freeze).
32
+ --bundle <file> Read the contracts from a built bundle instead of src/package.ts: an
33
+ isolated package.bundle.js, or a host-backed package's CommonJS bundle
34
+ (contract freeze).
35
+ `;
36
+ function readOptions(args) {
37
+ const options = { watch: false };
38
+ for (let index = 0; index < args.length; index += 1) {
39
+ const arg = args[index];
40
+ const value = () => {
41
+ const next = args[index + 1];
42
+ if (next === undefined || next.startsWith("--")) {
43
+ throw new Error(`${arg} needs a value`);
44
+ }
45
+ index += 1;
46
+ return next;
47
+ };
48
+ if (arg === "--watch")
49
+ options.watch = true;
50
+ else if (arg === "--root")
51
+ options.root = value();
52
+ else if (arg === "--entry")
53
+ options.entry = value();
54
+ else if (arg === "--out-dir")
55
+ options.outDir = value();
56
+ else
57
+ throw new Error(`Unknown option ${arg}`);
58
+ }
59
+ return options;
60
+ }
61
+ function summarize(result) {
62
+ const { manifest } = result;
63
+ const tools = manifest.provides.reduce((count, contract) => count + contract.tools.length, 0);
64
+ return `${manifest.provides.length} contract(s), ${tools} tool(s), ${manifest.tables?.length ?? 0} table(s)`;
65
+ }
66
+ function relative(file) {
67
+ return path.relative(process.cwd(), file) || file;
68
+ }
69
+ /**
70
+ * Run the `peers-package` command.
71
+ *
72
+ * @param argv - Arguments after the command name, such as `["build", "--watch"]`.
73
+ * @returns The exit code, or never resolves while watching.
74
+ */
75
+ async function main(argv) {
76
+ const [command, ...rest] = argv;
77
+ if (command === "contract") {
78
+ return contractCommand(rest);
79
+ }
80
+ if (command !== "manifest" && command !== "build") {
81
+ process.stdout.write(USAGE);
82
+ return command === undefined || command === "--help" ? 0 : 1;
83
+ }
84
+ const { watch, ...options } = readOptions(rest);
85
+ if (command === "manifest") {
86
+ if (watch) {
87
+ throw new Error("--watch applies to build");
88
+ }
89
+ const result = await (0, build_package_1.generatePackageManifest)(options);
90
+ console.log(`Manifest: ${summarize(result)} -> ${relative(result.manifestPath)}`);
91
+ return 0;
92
+ }
93
+ if (watch) {
94
+ await (0, build_package_1.watchPackage)(options, (result) => {
95
+ if (result instanceof Error) {
96
+ console.error(`Build failed: ${result.message}`);
97
+ return;
98
+ }
99
+ console.log(`Built ${relative(result.artifactPath)} (${summarize(result)}; provider ${Math.round(result.providerChars / 1024)} KB)`);
100
+ });
101
+ console.log("Watching for changes");
102
+ return new Promise(() => undefined);
103
+ }
104
+ const result = await (0, build_package_1.buildPackage)(options);
105
+ console.log(`Built ${relative(result.artifactPath)} (${summarize(result)}; provider ${Math.round(result.providerChars / 1024)} KB)`);
106
+ return 0;
107
+ }
108
+ /** `peers-package contract add <source> [options]` or `peers-package contract freeze [options]`. */
109
+ async function contractCommand(args) {
110
+ const [subcommand, source, ...rest] = args;
111
+ if (subcommand === "freeze")
112
+ return freezeCommand(args.slice(1));
113
+ if (subcommand !== "add" || source === undefined || source.startsWith("--")) {
114
+ process.stdout.write(USAGE);
115
+ return 1;
116
+ }
117
+ const options = { source };
118
+ for (let index = 0; index < rest.length; index += 1) {
119
+ const arg = rest[index];
120
+ const value = () => {
121
+ const next = rest[index + 1];
122
+ if (next === undefined || next.startsWith("--")) {
123
+ throw new Error(`${arg} needs a value`);
124
+ }
125
+ index += 1;
126
+ return next;
127
+ };
128
+ if (arg === "--check")
129
+ options.check = true;
130
+ else if (arg === "--root")
131
+ options.root = value();
132
+ else if (arg === "--out-dir")
133
+ options.outDir = value();
134
+ else if (arg === "--contract")
135
+ options.contract = value();
136
+ else if (arg === "--name")
137
+ options.name = value();
138
+ else
139
+ throw new Error(`Unknown option ${arg}`);
140
+ }
141
+ const result = await (0, contract_add_1.addContract)(options);
142
+ const file = relative(result.path);
143
+ if (options.check) {
144
+ if (!result.changed)
145
+ return 0;
146
+ const regenerate = ["peers-package", "contract", "add", source];
147
+ for (const [flag, value] of [
148
+ ["--root", options.root],
149
+ ["--out-dir", options.outDir],
150
+ ["--contract", options.contract],
151
+ ["--name", options.name],
152
+ ]) {
153
+ if (value !== undefined)
154
+ regenerate.push(flag, value);
155
+ }
156
+ const command = regenerate
157
+ .map((arg) => (/^[\w./:@#=-]+$/.test(arg) ? arg : `'${arg.replace(/'/g, "'\\''")}'`))
158
+ .join(" ");
159
+ console.error(`${file} is missing or out of date. Run: ${command}`);
160
+ return 1;
161
+ }
162
+ console.log(`${result.changed ? "Wrote" : "Unchanged:"} ${file} (${result.exportName})`);
163
+ return 0;
164
+ }
165
+ /** `peers-package contract freeze [--check] [--bundle <file>] [--root <dir>] [--entry <file>]`. */
166
+ async function freezeCommand(args) {
167
+ const options = {};
168
+ for (let index = 0; index < args.length; index += 1) {
169
+ const arg = args[index];
170
+ const value = () => {
171
+ const next = args[index + 1];
172
+ if (next === undefined || next.startsWith("--")) {
173
+ throw new Error(`${arg} needs a value`);
174
+ }
175
+ index += 1;
176
+ return next;
177
+ };
178
+ if (arg === "--check")
179
+ options.check = true;
180
+ else if (arg === "--bundle")
181
+ options.bundle = value();
182
+ else if (arg === "--root")
183
+ options.root = value();
184
+ else if (arg === "--entry")
185
+ options.entry = value();
186
+ else
187
+ throw new Error(`Unknown option ${arg}`);
188
+ }
189
+ const result = await (0, freeze_contracts_1.freezePackageContracts)(options);
190
+ const file = relative(result.path);
191
+ for (const contract of result.check.notProvided) {
192
+ console.log(`Note: ${contract.name} (${contract.contractId}@${contract.version}), frozen by ${contract.frozenBy}, is no longer provided.`);
193
+ }
194
+ if (result.check.changed.length > 0) {
195
+ console.error(`A frozen contract changed (${file}):`);
196
+ for (const { contract, changes } of result.check.changed) {
197
+ console.error(` ${contract.name} (${contract.contractId}@${contract.version}), frozen by ${contract.frozenBy}: ${changes.join("; ")}`);
198
+ }
199
+ console.error("Declare a new version of the contract instead.");
200
+ return 1;
201
+ }
202
+ if (options.check) {
203
+ // A package without the file has frozen nothing yet: the check passes but guards nothing.
204
+ console.log((0, node_fs_1.existsSync)(result.path)
205
+ ? `Frozen contracts unchanged (${file}).`
206
+ : `No ${file}: nothing is frozen yet. Run \`peers-package contract freeze\` before a stable release.`);
207
+ return 0;
208
+ }
209
+ if (!result.written) {
210
+ console.log(`Nothing new to freeze (${file}).`);
211
+ return 0;
212
+ }
213
+ for (const contract of result.added) {
214
+ console.log(`Froze ${contract.name} (${contract.contractId}@${contract.version}) as of ${contract.frozenBy}.`);
215
+ }
216
+ console.log(`Wrote ${file}. Commit it with the release.`);
217
+ return 0;
218
+ }
@@ -0,0 +1,35 @@
1
+ /** What {@link addContract} takes. */
2
+ export interface IContractAddOptions {
3
+ /**
4
+ * Where the contract comes from: a declaration module and its export
5
+ * (`../voice-hub/src/contracts/voice-skill.ts#VoiceSkill`), a provider's `manifest.json` or
6
+ * `package.bundle.js`, or the JSON `getContractDefinition` returned.
7
+ */
8
+ source: string;
9
+ /** `contractId@version`, for a manifest that provides more than one contract. */
10
+ contract?: string;
11
+ /** Package root. Defaults to the current directory. */
12
+ root?: string;
13
+ /** Where the file goes, relative to the root. Defaults to `src/contracts`. */
14
+ outDir?: string;
15
+ /** The declaration's export name, instead of the one made from the contract's name. */
16
+ name?: string;
17
+ /** Write nothing; report whether the file is missing or out of date. */
18
+ check?: boolean;
19
+ }
20
+ /** What {@link addContract} did. */
21
+ export interface IContractAddResult {
22
+ /** The declaration file's path. */
23
+ path: string;
24
+ /** Its export name. */
25
+ exportName: string;
26
+ /** The file was (or, with `check`, would be) written or changed. */
27
+ changed: boolean;
28
+ }
29
+ /**
30
+ * Write (or check) a generated declaration for a contract this package implements or calls:
31
+ * `src/contracts/<name>.v<version>.ts`, formatted with the package's Biome when it has one.
32
+ *
33
+ * @param options - The source, and where the file goes.
34
+ */
35
+ export declare function addContract(options: IContractAddOptions): Promise<IContractAddResult>;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.addContract = addContract;
4
+ const node_child_process_1 = require("node:child_process");
5
+ const node_fs_1 = require("node:fs");
6
+ const promises_1 = require("node:fs/promises");
7
+ const path = require("node:path");
8
+ const declare_contract_1 = require("../define/declare-contract");
9
+ const define_package_1 = require("../define/define-package");
10
+ const manifest_1 = require("../define/manifest");
11
+ const isolated_artifact_1 = require("../package-loader/isolated-artifact");
12
+ const build_package_1 = require("./build-package");
13
+ const contract_source_1 = require("./contract-source");
14
+ /**
15
+ * Write (or check) a generated declaration for a contract this package implements or calls:
16
+ * `src/contracts/<name>.v<version>.ts`, formatted with the package's Biome when it has one.
17
+ *
18
+ * @param options - The source, and where the file goes.
19
+ */
20
+ async function addContract(options) {
21
+ const root = path.resolve(options.root ?? process.cwd());
22
+ const contract = await readContractSource(root, options);
23
+ const generated = (0, contract_source_1.generateContractDeclaration)(contract, {
24
+ from: sourceLabel(root, options.source),
25
+ ...(options.name ? { exportName: options.name } : {}),
26
+ });
27
+ const target = path.join(path.resolve(root, options.outDir ?? "src/contracts"), generated.fileName);
28
+ const source = formatSource(root, target, generated.source);
29
+ const current = (0, node_fs_1.existsSync)(target) ? await (0, promises_1.readFile)(target, "utf8") : undefined;
30
+ const changed = current !== source;
31
+ if (changed && !options.check) {
32
+ await (0, promises_1.mkdir)(path.dirname(target), { recursive: true });
33
+ await (0, promises_1.writeFile)(target, source, "utf8");
34
+ }
35
+ return { path: target, exportName: generated.exportName, changed };
36
+ }
37
+ /** A source's file and, for a declaration module, its export name (`file.ts#Name`). */
38
+ function splitSource(source) {
39
+ const hash = source.lastIndexOf("#");
40
+ return hash > 0
41
+ ? { file: source.slice(0, hash), exportName: source.slice(hash + 1) }
42
+ : { file: source };
43
+ }
44
+ /**
45
+ * The source as the file's header names it: relative to the package root, with `/`, so the
46
+ * generated file (and `--check`) doesn't depend on where the command ran or on local paths.
47
+ */
48
+ function sourceLabel(root, source) {
49
+ const { file, exportName } = splitSource(source);
50
+ const relative = path.relative(root, path.resolve(file)).split(path.sep).join("/");
51
+ return exportName === undefined ? relative : `${relative}#${exportName}`;
52
+ }
53
+ async function readContractSource(root, options) {
54
+ const { file, exportName } = splitSource(options.source);
55
+ if (exportName !== undefined) {
56
+ const loaded = (await (0, build_package_1.loadPackageModule)(root, path.resolve(file)));
57
+ const value = loaded?.[exportName];
58
+ if ((0, declare_contract_1.isContractDeclaration)(value))
59
+ return (0, manifest_1.contractManifest)(value.declaration);
60
+ if ((0, define_package_1.isPackageContract)(value))
61
+ return (0, manifest_1.contractManifest)(value);
62
+ throw new Error(`${file} exports no declaration or contract named ${exportName}; pass <file>#<export>`);
63
+ }
64
+ const text = (await (0, promises_1.readFile)(path.resolve(options.source), "utf8")).trimStart();
65
+ if ((0, isolated_artifact_1.looksLikeIsolatedPackageArtifact)(text)) {
66
+ const artifact = (0, isolated_artifact_1.parseIsolatedPackageArtifact)(text);
67
+ return (0, contract_source_1.contractSourceFromManifest)(artifact.manifest, options.contract);
68
+ }
69
+ const json = JSON.parse(text);
70
+ if (Array.isArray(json.provides)) {
71
+ return (0, contract_source_1.contractSourceFromManifest)(json, options.contract);
72
+ }
73
+ if (json.definition) {
74
+ return (0, contract_source_1.contractSourceFromDefinition)(json);
75
+ }
76
+ if (typeof json.contractId === "string") {
77
+ throw new Error(`${options.source} is a contract definition without its tools' access levels; use getContractDefinition's result instead`);
78
+ }
79
+ throw new Error(`${options.source} isn't a manifest, a package artifact, or getContractDefinition's result`);
80
+ }
81
+ /** The source as the package's Biome formats it (imports sorted), or as generated without one. */
82
+ function formatSource(root, target, source) {
83
+ const shim = path.join(root, "node_modules", ".bin", "biome");
84
+ if (!(0, node_fs_1.existsSync)(shim))
85
+ return source;
86
+ const args = ["check", "--write", `--stdin-file-path=${target}`];
87
+ // Run Biome's Node script when it's there: Windows can't spawn the .bin shell shim itself.
88
+ const entry = path.join(root, "node_modules", "@biomejs", "biome", "bin", "biome");
89
+ const [command, commandArgs] = (0, node_fs_1.existsSync)(entry)
90
+ ? [process.execPath, [entry, ...args]]
91
+ : [shim, args];
92
+ const result = (0, node_child_process_1.spawnSync)(command, commandArgs, { cwd: root, input: source, encoding: "utf8" });
93
+ if (result.status !== 0 || !result.stdout) {
94
+ throw new Error(`Biome couldn't format the generated declaration: ${result.error?.message || result.stderr || result.stdout}`);
95
+ }
96
+ return result.stdout;
97
+ }
@@ -0,0 +1 @@
1
+ export {};