@apexops/capabilities 0.10.0-next.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 (130) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/bound-envelope-transport.d.ts +27 -0
  3. package/dist/bound-envelope-transport.d.ts.map +1 -0
  4. package/dist/bound-envelope-transport.js +96 -0
  5. package/dist/bound-envelope-transport.js.map +1 -0
  6. package/dist/capability.d.ts +36 -0
  7. package/dist/capability.d.ts.map +1 -0
  8. package/dist/capability.js +83 -0
  9. package/dist/capability.js.map +1 -0
  10. package/dist/command-plans.d.ts +49 -0
  11. package/dist/command-plans.d.ts.map +1 -0
  12. package/dist/command-plans.js +157 -0
  13. package/dist/command-plans.js.map +1 -0
  14. package/dist/design-capabilities.d.ts +23 -0
  15. package/dist/design-capabilities.d.ts.map +1 -0
  16. package/dist/design-capabilities.js +32 -0
  17. package/dist/design-capabilities.js.map +1 -0
  18. package/dist/encrypted-envelope-transport.d.ts +32 -0
  19. package/dist/encrypted-envelope-transport.d.ts.map +1 -0
  20. package/dist/encrypted-envelope-transport.js +138 -0
  21. package/dist/encrypted-envelope-transport.js.map +1 -0
  22. package/dist/iac-generation.d.ts +52 -0
  23. package/dist/iac-generation.d.ts.map +1 -0
  24. package/dist/iac-generation.js +645 -0
  25. package/dist/iac-generation.js.map +1 -0
  26. package/dist/iac-normalizers.d.ts +22 -0
  27. package/dist/iac-normalizers.d.ts.map +1 -0
  28. package/dist/iac-normalizers.js +198 -0
  29. package/dist/iac-normalizers.js.map +1 -0
  30. package/dist/iac.d.ts +83 -0
  31. package/dist/iac.d.ts.map +1 -0
  32. package/dist/iac.js +217 -0
  33. package/dist/iac.js.map +1 -0
  34. package/dist/index.d.ts +17 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +17 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/local-plan-transport.d.ts +29 -0
  39. package/dist/local-plan-transport.d.ts.map +1 -0
  40. package/dist/local-plan-transport.js +101 -0
  41. package/dist/local-plan-transport.js.map +1 -0
  42. package/dist/native-iac-providers.d.ts +96 -0
  43. package/dist/native-iac-providers.d.ts.map +1 -0
  44. package/dist/native-iac-providers.js +487 -0
  45. package/dist/native-iac-providers.js.map +1 -0
  46. package/dist/operations-placeholders.d.ts +5 -0
  47. package/dist/operations-placeholders.d.ts.map +1 -0
  48. package/dist/operations-placeholders.js +23 -0
  49. package/dist/operations-placeholders.js.map +1 -0
  50. package/dist/pack-manager.d.ts +95 -0
  51. package/dist/pack-manager.d.ts.map +1 -0
  52. package/dist/pack-manager.js +556 -0
  53. package/dist/pack-manager.js.map +1 -0
  54. package/dist/packs.d.ts +29 -0
  55. package/dist/packs.d.ts.map +1 -0
  56. package/dist/packs.js +51 -0
  57. package/dist/packs.js.map +1 -0
  58. package/dist/process-runner.d.ts +30 -0
  59. package/dist/process-runner.d.ts.map +1 -0
  60. package/dist/process-runner.js +97 -0
  61. package/dist/process-runner.js.map +1 -0
  62. package/dist/secret-redaction.d.ts +3 -0
  63. package/dist/secret-redaction.d.ts.map +1 -0
  64. package/dist/secret-redaction.js +48 -0
  65. package/dist/secret-redaction.js.map +1 -0
  66. package/dist/terraform-provider-schema.d.ts +75 -0
  67. package/dist/terraform-provider-schema.d.ts.map +1 -0
  68. package/dist/terraform-provider-schema.js +307 -0
  69. package/dist/terraform-provider-schema.js.map +1 -0
  70. package/dist/terraform-registry-client.d.ts +63 -0
  71. package/dist/terraform-registry-client.d.ts.map +1 -0
  72. package/dist/terraform-registry-client.js +388 -0
  73. package/dist/terraform-registry-client.js.map +1 -0
  74. package/dist/test/bound-envelope-transport.test.d.ts +2 -0
  75. package/dist/test/bound-envelope-transport.test.d.ts.map +1 -0
  76. package/dist/test/bound-envelope-transport.test.js +30 -0
  77. package/dist/test/bound-envelope-transport.test.js.map +1 -0
  78. package/dist/test/capabilities.test.d.ts +2 -0
  79. package/dist/test/capabilities.test.d.ts.map +1 -0
  80. package/dist/test/capabilities.test.js +86 -0
  81. package/dist/test/capabilities.test.js.map +1 -0
  82. package/dist/test/encrypted-envelope-transport.test.d.ts +2 -0
  83. package/dist/test/encrypted-envelope-transport.test.d.ts.map +1 -0
  84. package/dist/test/encrypted-envelope-transport.test.js +74 -0
  85. package/dist/test/encrypted-envelope-transport.test.js.map +1 -0
  86. package/dist/test/fixtures/terraform-provider-schema.d.ts +65 -0
  87. package/dist/test/fixtures/terraform-provider-schema.d.ts.map +1 -0
  88. package/dist/test/fixtures/terraform-provider-schema.js +29 -0
  89. package/dist/test/fixtures/terraform-provider-schema.js.map +1 -0
  90. package/dist/test/fixtures/terraform-registry.d.ts +54 -0
  91. package/dist/test/fixtures/terraform-registry.d.ts.map +1 -0
  92. package/dist/test/fixtures/terraform-registry.js +42 -0
  93. package/dist/test/fixtures/terraform-registry.js.map +1 -0
  94. package/dist/test/iac-generation.test.d.ts +2 -0
  95. package/dist/test/iac-generation.test.d.ts.map +1 -0
  96. package/dist/test/iac-generation.test.js +271 -0
  97. package/dist/test/iac-generation.test.js.map +1 -0
  98. package/dist/test/iac.test.d.ts +2 -0
  99. package/dist/test/iac.test.d.ts.map +1 -0
  100. package/dist/test/iac.test.js +161 -0
  101. package/dist/test/iac.test.js.map +1 -0
  102. package/dist/test/native-iac-providers.test.d.ts +2 -0
  103. package/dist/test/native-iac-providers.test.d.ts.map +1 -0
  104. package/dist/test/native-iac-providers.test.js +594 -0
  105. package/dist/test/native-iac-providers.test.js.map +1 -0
  106. package/dist/test/operations-placeholders.test.d.ts +2 -0
  107. package/dist/test/operations-placeholders.test.d.ts.map +1 -0
  108. package/dist/test/operations-placeholders.test.js +34 -0
  109. package/dist/test/operations-placeholders.test.js.map +1 -0
  110. package/dist/test/pack-manager.test.d.ts +2 -0
  111. package/dist/test/pack-manager.test.d.ts.map +1 -0
  112. package/dist/test/pack-manager.test.js +379 -0
  113. package/dist/test/pack-manager.test.js.map +1 -0
  114. package/dist/test/packs-and-commands.test.d.ts +2 -0
  115. package/dist/test/packs-and-commands.test.d.ts.map +1 -0
  116. package/dist/test/packs-and-commands.test.js +131 -0
  117. package/dist/test/packs-and-commands.test.js.map +1 -0
  118. package/dist/test/process-runner.test.d.ts +2 -0
  119. package/dist/test/process-runner.test.d.ts.map +1 -0
  120. package/dist/test/process-runner.test.js +42 -0
  121. package/dist/test/process-runner.test.js.map +1 -0
  122. package/dist/test/terraform-provider-schema.test.d.ts +2 -0
  123. package/dist/test/terraform-provider-schema.test.d.ts.map +1 -0
  124. package/dist/test/terraform-provider-schema.test.js +259 -0
  125. package/dist/test/terraform-provider-schema.test.js.map +1 -0
  126. package/dist/test/terraform-registry-client.test.d.ts +2 -0
  127. package/dist/test/terraform-registry-client.test.d.ts.map +1 -0
  128. package/dist/test/terraform-registry-client.test.js +321 -0
  129. package/dist/test/terraform-registry-client.test.js.map +1 -0
  130. package/package.json +41 -0
@@ -0,0 +1,95 @@
1
+ import type { ProcessRunnerLike } from "./process-runner.js";
2
+ export type CapabilityPackRuntime = "python" | "deno" | "npm";
3
+ export type CapabilityPackState = "installed" | "unavailable" | "blocked" | "invalid" | "not-installed";
4
+ export interface CapabilityPackArtifactSpec {
5
+ readonly type: "local-directory" | "npm-tarball";
6
+ readonly spec: string;
7
+ readonly digest: string;
8
+ readonly integrity?: string;
9
+ }
10
+ export interface CapabilityPackLockSpec {
11
+ readonly installer: "uv" | "pip-hashes" | "deno";
12
+ readonly path?: string;
13
+ readonly digest: string;
14
+ readonly directDigest: string;
15
+ readonly transitiveDigest: string;
16
+ }
17
+ export interface CapabilityPackExecutableSpec {
18
+ readonly command: string;
19
+ readonly args: readonly string[];
20
+ }
21
+ export interface CapabilityPackDefinition {
22
+ readonly id: string;
23
+ readonly version?: string;
24
+ readonly runtime?: CapabilityPackRuntime;
25
+ readonly implementation?: CapabilityPackRuntime;
26
+ readonly source?: string;
27
+ readonly artifact?: CapabilityPackArtifactSpec;
28
+ readonly lock?: CapabilityPackLockSpec;
29
+ readonly executable?: CapabilityPackExecutableSpec;
30
+ readonly capabilities?: readonly string[];
31
+ readonly requires?: readonly string[];
32
+ readonly requiredWorkflows?: readonly string[];
33
+ readonly dependencyFree?: boolean;
34
+ readonly script?: string;
35
+ readonly scriptDigest?: string;
36
+ }
37
+ export interface CapabilityPackRegistryV1 {
38
+ readonly schemaVersion: string;
39
+ readonly protocolVersion?: string;
40
+ readonly packs: readonly CapabilityPackDefinition[];
41
+ }
42
+ export interface CapabilityPackLockV1 {
43
+ readonly schemaVersion: "1.0.0";
44
+ readonly id: string;
45
+ readonly version: string;
46
+ readonly digest: string;
47
+ readonly capabilities: readonly string[];
48
+ readonly entrypoints: Readonly<Record<string, string>>;
49
+ readonly requires: readonly string[];
50
+ readonly runtime: CapabilityPackRuntime;
51
+ readonly source: string;
52
+ readonly sourceArtifactDigest: string;
53
+ readonly installationManifestDigest: string;
54
+ readonly artifactDigest: string;
55
+ readonly directLockDigest: string;
56
+ readonly transitiveLockDigest: string;
57
+ readonly executable: CapabilityPackExecutableSpec;
58
+ readonly installedAt?: string;
59
+ }
60
+ export interface CapabilityPackStatusResult {
61
+ readonly id: string;
62
+ readonly state: CapabilityPackState;
63
+ readonly version?: string;
64
+ readonly installedVersion?: string;
65
+ readonly requiredWorkflows: readonly string[];
66
+ readonly reason?: string;
67
+ readonly action: string;
68
+ }
69
+ export interface CapabilityPackOperationResult extends CapabilityPackStatusResult {
70
+ readonly changed: boolean;
71
+ readonly lock?: CapabilityPackLockV1;
72
+ }
73
+ export interface CapabilityPackManagerOptions {
74
+ readonly root: string;
75
+ readonly manifestPath?: string;
76
+ readonly processRunner: ProcessRunnerLike;
77
+ readonly clock?: () => Date;
78
+ readonly idSource?: () => string;
79
+ }
80
+ export interface CapabilityPackInstallOptions {
81
+ readonly cacheDeno?: boolean;
82
+ }
83
+ export declare class CapabilityPackManager {
84
+ #private;
85
+ constructor(options: CapabilityPackManagerOptions);
86
+ list(): Promise<readonly CapabilityPackStatusResult[]>;
87
+ status(id: string): Promise<CapabilityPackStatusResult>;
88
+ install(id: string, options?: CapabilityPackInstallOptions): Promise<CapabilityPackOperationResult>;
89
+ update(id: string, options?: CapabilityPackInstallOptions): Promise<CapabilityPackOperationResult>;
90
+ rollback(id: string): Promise<CapabilityPackOperationResult>;
91
+ verify(id: string): Promise<CapabilityPackOperationResult>;
92
+ uninstall(id: string): Promise<CapabilityPackOperationResult>;
93
+ requiredForWorkflows(workflows: readonly string[]): Promise<readonly CapabilityPackStatusResult[]>;
94
+ }
95
+ //# sourceMappingURL=pack-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pack-manager.d.ts","sourceRoot":"","sources":["../src/pack-manager.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAQ7D,MAAM,MAAM,qBAAqB,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC;AAC9D,MAAM,MAAM,mBAAmB,GAAG,WAAW,GAAG,aAAa,GAAG,SAAS,GAAG,SAAS,GAAG,eAAe,CAAC;AAExG,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,IAAI,EAAE,iBAAiB,GAAG,aAAa,CAAC;IACjD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,SAAS,EAAE,IAAI,GAAG,YAAY,GAAG,MAAM,CAAC;IACjD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;CACnC;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,CAAC,EAAE,qBAAqB,CAAC;IACzC,QAAQ,CAAC,cAAc,CAAC,EAAE,qBAAqB,CAAC;IAChD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,0BAA0B,CAAC;IAC/C,QAAQ,CAAC,IAAI,CAAC,EAAE,sBAAsB,CAAC;IACvC,QAAQ,CAAC,UAAU,CAAC,EAAE,4BAA4B,CAAC;IACnD,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/C,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,SAAS,wBAAwB,EAAE,CAAC;CACrD;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACvD,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,qBAAqB,CAAC;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,0BAA0B,EAAE,MAAM,CAAC;IAC5C,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,UAAU,EAAE,4BAA4B,CAAC;IAClD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,mBAAmB,CAAC;IACpC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,6BAA8B,SAAQ,0BAA0B;IAC/E,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,oBAAoB,CAAC;CACtC;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,aAAa,EAAE,iBAAiB,CAAC;IAC1C,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;CAC9B;AAWD,qBAAa,qBAAqB;;IAOhC,YAAY,OAAO,EAAE,4BAA4B,EAQhD;IAEK,IAAI,IAAI,OAAO,CAAC,SAAS,0BAA0B,EAAE,CAAC,CAG3D;IAEK,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAE5D;IAEK,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,4BAAiC,GAAG,OAAO,CAAC,6BAA6B,CAAC,CAK5G;IAEK,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,4BAAiC,GAAG,OAAO,CAAC,6BAA6B,CAAC,CAK3G;IAEK,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,6BAA6B,CAAC,CA4CjE;IAEK,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,6BAA6B,CAAC,CAmD/D;IAEK,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,6BAA6B,CAAC,CAelE;IAEK,oBAAoB,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,0BAA0B,EAAE,CAAC,CAQvG;CA6YF"}
@@ -0,0 +1,556 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { constants } from "node:fs";
3
+ import { access, cp, lstat, mkdir, readFile, readdir, realpath, rename, rm, writeFile } from "node:fs/promises";
4
+ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
5
+ const HASH = /^[0-9a-f]{64}$/;
6
+ const EMPTY_DIGEST = createHash("sha256").update("").digest("hex");
7
+ const DEFAULT_TIMEOUT_MS = 120_000;
8
+ const DEFAULT_OUTPUT_BYTES = 1024 * 1024;
9
+ const compareText = (left, right) => (left < right ? -1 : left > right ? 1 : 0);
10
+ export class CapabilityPackManager {
11
+ #root;
12
+ #manifestPath;
13
+ #runner;
14
+ #clock;
15
+ #idSource;
16
+ constructor(options) {
17
+ this.#root = resolve(options.root);
18
+ this.#manifestPath = resolve(options.manifestPath ?? join(this.#root, ".apex", "runtime", "capability-packs.v1.json"));
19
+ this.#runner = options.processRunner;
20
+ this.#clock = options.clock;
21
+ this.#idSource = options.idSource ?? randomUUID;
22
+ }
23
+ async list() {
24
+ const registry = await this.#registry();
25
+ return await Promise.all(registry.packs.map(async (pack) => await this.#status(pack)));
26
+ }
27
+ async status(id) {
28
+ return await this.#status(await this.#definition(id));
29
+ }
30
+ async install(id, options = {}) {
31
+ const definition = await this.#definition(id);
32
+ const prepared = await this.#prepare(definition);
33
+ if ("state" in prepared)
34
+ return { ...prepared, changed: false };
35
+ return await this.#installPrepared(prepared, options, false);
36
+ }
37
+ async update(id, options = {}) {
38
+ const definition = await this.#definition(id);
39
+ const prepared = await this.#prepare(definition);
40
+ if ("state" in prepared)
41
+ return { ...prepared, changed: false };
42
+ return await this.#installPrepared(prepared, options, true);
43
+ }
44
+ async rollback(id) {
45
+ this.#assertId(id);
46
+ const target = this.#packDirectory(id);
47
+ const previous = `${target}.previous`;
48
+ if (!(await this.#exists(previous))) {
49
+ return {
50
+ ...(await this.status(id)),
51
+ state: "blocked",
52
+ reason: "rollback-unavailable",
53
+ action: "No previous pack is available",
54
+ changed: false,
55
+ };
56
+ }
57
+ const staged = join(this.#localDirectory(), `rollback-${id}-${this.#idSource()}`);
58
+ await mkdir(dirname(staged), { recursive: true });
59
+ let previousIsStaged = false;
60
+ let currentIsPrevious = false;
61
+ let previousIsCurrent = false;
62
+ try {
63
+ await rename(previous, staged);
64
+ previousIsStaged = true;
65
+ if (await this.#exists(target)) {
66
+ await rename(target, previous);
67
+ currentIsPrevious = true;
68
+ }
69
+ await rename(staged, target);
70
+ previousIsStaged = false;
71
+ previousIsCurrent = true;
72
+ const verified = await this.verify(id);
73
+ if (verified.state !== "installed")
74
+ throw new Error(verified.reason ?? "rollback verification failed");
75
+ return { ...verified, changed: true, lock: await this.#readLock(target) };
76
+ }
77
+ catch (error) {
78
+ if (previousIsCurrent) {
79
+ await rename(target, staged);
80
+ if (currentIsPrevious)
81
+ await rename(previous, target);
82
+ await rename(staged, previous);
83
+ }
84
+ else if (currentIsPrevious) {
85
+ await rename(previous, target);
86
+ await rename(staged, previous);
87
+ }
88
+ else if (previousIsStaged) {
89
+ await rename(staged, previous);
90
+ }
91
+ throw error;
92
+ }
93
+ }
94
+ async verify(id) {
95
+ this.#assertId(id);
96
+ const directory = this.#packDirectory(id);
97
+ if (!(await this.#exists(directory))) {
98
+ return {
99
+ ...(await this.status(id)),
100
+ state: "not-installed",
101
+ action: `Run apex capability install --pack ${id}`,
102
+ changed: false,
103
+ };
104
+ }
105
+ try {
106
+ await this.#assertTreeSafe(directory);
107
+ const lock = await this.#readLock(directory);
108
+ if (lock.id !== id ||
109
+ !HASH.test(lock.digest) ||
110
+ !HASH.test(lock.sourceArtifactDigest) ||
111
+ !HASH.test(lock.installationManifestDigest) ||
112
+ !HASH.test(lock.artifactDigest) ||
113
+ !HASH.test(lock.directLockDigest) ||
114
+ !HASH.test(lock.transitiveLockDigest)) {
115
+ throw new Error("invalid pack lock");
116
+ }
117
+ const sourceDigest = await this.#treeDigest(directory, this.#generatedPaths());
118
+ if (sourceDigest !== lock.sourceArtifactDigest || sourceDigest !== lock.artifactDigest)
119
+ throw new Error("source artifact digest mismatch");
120
+ if (this.#installationManifestDigest(lock) !== lock.installationManifestDigest)
121
+ throw new Error("installation manifest digest mismatch");
122
+ await this.#assertInstalledExecutable(directory, lock);
123
+ return {
124
+ id,
125
+ state: "installed",
126
+ version: lock.version,
127
+ installedVersion: lock.version,
128
+ requiredWorkflows: (await this.#optionalDefinition(id))?.requiredWorkflows ?? [],
129
+ action: "No action required",
130
+ changed: false,
131
+ lock,
132
+ };
133
+ }
134
+ catch (error) {
135
+ return {
136
+ id,
137
+ state: "invalid",
138
+ requiredWorkflows: (await this.#optionalDefinition(id))?.requiredWorkflows ?? [],
139
+ reason: error instanceof Error ? error.message : "verification failed",
140
+ action: `Run apex capability update --pack ${id}`,
141
+ changed: false,
142
+ };
143
+ }
144
+ }
145
+ async uninstall(id) {
146
+ const definition = await this.#definition(id);
147
+ const target = this.#packDirectory(id);
148
+ const previous = `${target}.previous`;
149
+ const changed = (await this.#exists(target)) || (await this.#exists(previous));
150
+ await rm(target, { recursive: true, force: true });
151
+ await rm(previous, { recursive: true, force: true });
152
+ return {
153
+ id,
154
+ state: "not-installed",
155
+ ...(definition.version === undefined ? {} : { version: definition.version }),
156
+ requiredWorkflows: definition.requiredWorkflows ?? [],
157
+ action: `Run apex capability install --pack ${id}`,
158
+ changed,
159
+ };
160
+ }
161
+ async requiredForWorkflows(workflows) {
162
+ const selected = new Set(workflows);
163
+ const registry = await this.#registry();
164
+ return await Promise.all(registry.packs
165
+ .filter((pack) => (pack.requiredWorkflows ?? []).some((workflow) => selected.has(workflow)))
166
+ .map(async (pack) => await this.#status(pack)));
167
+ }
168
+ async #installPrepared(prepared, options, replacing) {
169
+ const { definition, runtime, artifact, executable, lock, version } = prepared;
170
+ const target = this.#packDirectory(definition.id);
171
+ if (!replacing && (await this.#exists(target)))
172
+ return { ...(await this.verify(definition.id)), changed: false };
173
+ const staged = join(this.#localDirectory(), `capability-pack-${definition.id}-${this.#idSource()}`);
174
+ const previous = `${target}.previous`;
175
+ await mkdir(dirname(staged), { recursive: true });
176
+ try {
177
+ if (artifact.type === "local-directory") {
178
+ const source = this.#resolveManifestPath(artifact.spec);
179
+ await this.#assertTreeSafe(source);
180
+ if ((await this.#treeDigest(source)) !== artifact.digest)
181
+ throw new Error("artifact digest mismatch");
182
+ await cp(source, staged, { recursive: true, dereference: false, errorOnExist: true, force: false });
183
+ }
184
+ else {
185
+ await mkdir(staged);
186
+ await this.#runner.run({
187
+ executable: "npm",
188
+ args: [
189
+ "install",
190
+ "--ignore-scripts",
191
+ "--no-audit",
192
+ "--no-fund",
193
+ "--save-exact",
194
+ `--integrity=${artifact.integrity}`,
195
+ artifact.spec,
196
+ ],
197
+ cwd: staged,
198
+ timeoutMs: DEFAULT_TIMEOUT_MS,
199
+ maxOutputBytes: DEFAULT_OUTPUT_BYTES,
200
+ });
201
+ await this.#assertTreeSafe(staged);
202
+ if ((await this.#treeDigest(staged)) !== artifact.digest)
203
+ throw new Error("artifact digest mismatch");
204
+ }
205
+ await this.#assertTreeSafe(staged);
206
+ await this.#installRuntime(prepared, staged, options);
207
+ const sourceArtifactDigest = await this.#treeDigest(staged, this.#generatedPaths(false));
208
+ if (sourceArtifactDigest !== artifact.digest)
209
+ throw new Error("source artifact digest changed during install");
210
+ const lockBase = {
211
+ schemaVersion: "1.0.0",
212
+ id: definition.id,
213
+ version,
214
+ digest: sourceArtifactDigest,
215
+ capabilities: definition.capabilities ?? [definition.id],
216
+ entrypoints: { default: executable.command },
217
+ requires: definition.requires ?? [],
218
+ runtime,
219
+ source: artifact.spec,
220
+ sourceArtifactDigest,
221
+ artifactDigest: artifact.digest,
222
+ directLockDigest: lock.directDigest,
223
+ transitiveLockDigest: lock.transitiveDigest,
224
+ executable,
225
+ ...(this.#clock === undefined ? {} : { installedAt: this.#clock().toISOString() }),
226
+ };
227
+ const packLock = {
228
+ ...lockBase,
229
+ installationManifestDigest: this.#installationManifestDigest(lockBase),
230
+ };
231
+ await writeFile(join(staged, "pack.lock.json"), `${JSON.stringify(packLock, null, 2)}\n`, { flag: "wx" });
232
+ await this.#assertTreeSafe(staged);
233
+ await mkdir(dirname(target), { recursive: true });
234
+ await rm(previous, { recursive: true, force: true });
235
+ if (await this.#exists(target))
236
+ await rename(target, previous);
237
+ try {
238
+ await rename(staged, target);
239
+ }
240
+ catch (error) {
241
+ if (await this.#exists(previous))
242
+ await rename(previous, target);
243
+ throw error;
244
+ }
245
+ const verified = await this.verify(definition.id);
246
+ if (verified.state !== "installed") {
247
+ await rm(target, { recursive: true, force: true });
248
+ if (await this.#exists(previous))
249
+ await rename(previous, target);
250
+ throw new Error(verified.reason ?? "installed pack verification failed");
251
+ }
252
+ return { ...verified, changed: true, lock: packLock };
253
+ }
254
+ catch (error) {
255
+ await rm(staged, { recursive: true, force: true });
256
+ throw error;
257
+ }
258
+ }
259
+ async #installRuntime(prepared, staged, options) {
260
+ const { definition, runtime, lock } = prepared;
261
+ const lockPath = lock.path === undefined ? undefined : this.#resolveManifestPath(lock.path);
262
+ if (lockPath !== undefined && (await this.#fileDigest(lockPath)) !== lock.digest)
263
+ throw new Error("lock digest mismatch");
264
+ if (runtime === "python") {
265
+ if (definition.dependencyFree === true) {
266
+ const script = this.#safeRelative(definition.script ?? prepared.executable.args.at(-1) ?? "");
267
+ const scriptPath = join(staged, script);
268
+ if (!HASH.test(definition.scriptDigest ?? "") ||
269
+ (await this.#fileDigest(scriptPath)) !== definition.scriptDigest)
270
+ throw new Error("script digest mismatch");
271
+ return;
272
+ }
273
+ if (lockPath === undefined)
274
+ throw new Error("lock-unavailable");
275
+ if (lock.installer === "uv") {
276
+ const stagedLock = join(staged, this.#safeRelative(relative(this.#resolveManifestPath(prepared.artifact.spec), lockPath)));
277
+ if ((await this.#fileDigest(stagedLock)) !== lock.digest)
278
+ throw new Error("staged lock digest mismatch");
279
+ try {
280
+ await this.#runner.run({
281
+ executable: "uv",
282
+ args: ["sync", "--frozen", "--no-dev"],
283
+ cwd: staged,
284
+ timeoutMs: DEFAULT_TIMEOUT_MS,
285
+ maxOutputBytes: DEFAULT_OUTPUT_BYTES,
286
+ });
287
+ }
288
+ catch (error) {
289
+ if (error instanceof Error && /ENOENT|not found|spawn uv/u.test(error.message))
290
+ throw new Error("uv is required to install this capability pack; install uv and ensure it is on PATH", {
291
+ cause: error,
292
+ });
293
+ throw error;
294
+ }
295
+ if ((await this.#fileDigest(stagedLock)) !== lock.digest)
296
+ throw new Error("staged lock changed during uv sync");
297
+ return;
298
+ }
299
+ if (lock.installer !== "pip-hashes")
300
+ throw new Error("Python capability pack requires uv or pip-hashes installer");
301
+ await this.#runner.run({
302
+ executable: "python",
303
+ args: ["-m", "venv", ".venv"],
304
+ cwd: staged,
305
+ timeoutMs: DEFAULT_TIMEOUT_MS,
306
+ maxOutputBytes: DEFAULT_OUTPUT_BYTES,
307
+ });
308
+ await this.#runner.run({
309
+ executable: join(staged, ".venv", process.platform === "win32" ? "Scripts" : "bin", "python"),
310
+ args: ["-m", "pip", "install", "--require-hashes", "-r", lockPath],
311
+ cwd: staged,
312
+ timeoutMs: DEFAULT_TIMEOUT_MS,
313
+ maxOutputBytes: DEFAULT_OUTPUT_BYTES,
314
+ });
315
+ return;
316
+ }
317
+ if (runtime === "deno" && options.cacheDeno === true) {
318
+ if (lockPath === undefined)
319
+ throw new Error("lock-unavailable");
320
+ if (lock.installer !== "deno")
321
+ throw new Error("Deno capability pack requires deno installer");
322
+ const stagedLock = join(staged, this.#safeRelative(relative(this.#resolveManifestPath(prepared.artifact.spec), lockPath)));
323
+ if ((await this.#fileDigest(stagedLock)) !== lock.digest)
324
+ throw new Error("staged lock digest mismatch");
325
+ await this.#runner.run({
326
+ executable: "deno",
327
+ args: ["cache", "--frozen", `--lock=${stagedLock}`, prepared.executable.args.at(-1)],
328
+ cwd: staged,
329
+ env: { ...process.env, DENO_DIR: join(staged, ".deno") },
330
+ timeoutMs: DEFAULT_TIMEOUT_MS,
331
+ maxOutputBytes: DEFAULT_OUTPUT_BYTES,
332
+ });
333
+ if ((await this.#fileDigest(stagedLock)) !== lock.digest)
334
+ throw new Error("staged lock changed during deno cache");
335
+ }
336
+ }
337
+ async #prepare(definition) {
338
+ const unavailableBase = { id: definition.id, requiredWorkflows: definition.requiredWorkflows ?? [] };
339
+ const runtime = definition.runtime ?? definition.implementation;
340
+ if (definition.version === undefined || runtime === undefined || definition.executable === undefined) {
341
+ return {
342
+ ...unavailableBase,
343
+ state: "unavailable",
344
+ reason: "source-only",
345
+ action: "This bundled pack has no published artifact metadata; install a distribution that supplies version, artifact, lock, and executable fields",
346
+ };
347
+ }
348
+ const base = { ...unavailableBase, version: definition.version };
349
+ let artifact = definition.artifact;
350
+ if (artifact === undefined && definition.source !== undefined) {
351
+ const source = this.#resolveManifestPath(definition.source);
352
+ if (await this.#exists(source))
353
+ artifact = { type: "local-directory", spec: definition.source, digest: await this.#treeDigest(source) };
354
+ }
355
+ if (artifact === undefined || !HASH.test(artifact.digest)) {
356
+ return {
357
+ ...base,
358
+ state: "unavailable",
359
+ reason: "artifact-unavailable",
360
+ action: "Provide a digest-pinned local directory or external artifact",
361
+ };
362
+ }
363
+ if (artifact.type === "npm-tarball" && (artifact.integrity === undefined || !artifact.spec.endsWith(".tgz"))) {
364
+ return {
365
+ ...base,
366
+ state: "blocked",
367
+ reason: "integrity-unavailable",
368
+ action: "Provide an exact npm tarball spec and integrity",
369
+ };
370
+ }
371
+ if (definition.lock === undefined && runtime === "python" && definition.dependencyFree === true) {
372
+ return {
373
+ definition,
374
+ runtime,
375
+ version: definition.version,
376
+ artifact,
377
+ executable: definition.executable,
378
+ lock: {
379
+ installer: "pip-hashes",
380
+ digest: EMPTY_DIGEST,
381
+ directDigest: EMPTY_DIGEST,
382
+ transitiveDigest: EMPTY_DIGEST,
383
+ },
384
+ };
385
+ }
386
+ if (definition.lock === undefined) {
387
+ return {
388
+ ...base,
389
+ state: "blocked",
390
+ reason: "lock-unavailable",
391
+ action: runtime === "python" ? "Provide a hash-locked requirements file" : "Provide a verified runtime lock",
392
+ };
393
+ }
394
+ const lock = definition.lock;
395
+ if (![lock.digest, lock.directDigest, lock.transitiveDigest].every((digest) => HASH.test(digest))) {
396
+ return {
397
+ ...base,
398
+ state: "blocked",
399
+ reason: "lock-unavailable",
400
+ action: "Provide valid lock, direct dependency, and transitive dependency digests",
401
+ };
402
+ }
403
+ return { definition, runtime, version: definition.version, artifact, executable: definition.executable, lock };
404
+ }
405
+ async #status(definition) {
406
+ const directory = this.#packDirectory(definition.id);
407
+ if (await this.#exists(directory))
408
+ return await this.verify(definition.id);
409
+ const prepared = await this.#prepare(definition);
410
+ if ("state" in prepared)
411
+ return prepared;
412
+ return {
413
+ id: definition.id,
414
+ state: "not-installed",
415
+ version: prepared.version,
416
+ requiredWorkflows: definition.requiredWorkflows ?? [],
417
+ action: `Run apex capability install --pack ${definition.id}`,
418
+ };
419
+ }
420
+ async #registry() {
421
+ const value = JSON.parse(await readFile(this.#manifestPath, "utf8"));
422
+ if (!Array.isArray(value.packs))
423
+ throw new Error("Capability pack manifest must contain packs[]");
424
+ const ids = new Set();
425
+ for (const pack of value.packs) {
426
+ this.#assertId(pack.id);
427
+ if (ids.has(pack.id))
428
+ throw new Error(`Duplicate capability pack '${pack.id}'`);
429
+ ids.add(pack.id);
430
+ }
431
+ return value;
432
+ }
433
+ async #definition(id) {
434
+ this.#assertId(id);
435
+ const definition = (await this.#registry()).packs.find((pack) => pack.id === id);
436
+ if (definition === undefined)
437
+ throw new Error(`Unknown capability pack '${id}'`);
438
+ return definition;
439
+ }
440
+ async #optionalDefinition(id) {
441
+ try {
442
+ return await this.#definition(id);
443
+ }
444
+ catch {
445
+ return undefined;
446
+ }
447
+ }
448
+ #assertId(id) {
449
+ if (!/^[a-z0-9][a-z0-9-]{0,63}$/.test(id))
450
+ throw new Error(`Unsafe capability pack id '${id}'`);
451
+ }
452
+ #safeRelative(path) {
453
+ if (path.length === 0 || isAbsolute(path) || path.split(/[\\/]/u).includes(".."))
454
+ throw new Error(`Unsafe relative path '${path}'`);
455
+ return path;
456
+ }
457
+ #resolveManifestPath(path) {
458
+ const manifestRoot = dirname(this.#manifestPath);
459
+ const candidate = resolve(manifestRoot, this.#safeRelative(path));
460
+ if (candidate !== manifestRoot && !candidate.startsWith(`${manifestRoot}${sep}`))
461
+ throw new Error(`Capability pack path escapes manifest root: ${path}`);
462
+ return candidate;
463
+ }
464
+ #packDirectory(id) {
465
+ return join(this.#root, ".apex", "capability-packs", id);
466
+ }
467
+ #localDirectory() {
468
+ return join(this.#root, ".apex", "local");
469
+ }
470
+ async #readLock(directory) {
471
+ return JSON.parse(await readFile(join(directory, "pack.lock.json"), "utf8"));
472
+ }
473
+ async #exists(path) {
474
+ try {
475
+ await access(path, constants.F_OK);
476
+ return true;
477
+ }
478
+ catch {
479
+ return false;
480
+ }
481
+ }
482
+ async #fileDigest(path) {
483
+ return createHash("sha256")
484
+ .update(await readFile(path))
485
+ .digest("hex");
486
+ }
487
+ #generatedPaths(includePackLock = true) {
488
+ return new Set([...(includePackLock ? ["pack.lock.json"] : []), ".venv", "node_modules", ".deno", "deno-dir"]);
489
+ }
490
+ #installationManifestDigest(lock) {
491
+ return createHash("sha256")
492
+ .update(JSON.stringify({
493
+ sourceArtifactDigest: lock.sourceArtifactDigest,
494
+ directLockDigest: lock.directLockDigest,
495
+ transitiveLockDigest: lock.transitiveLockDigest,
496
+ executable: lock.executable,
497
+ }))
498
+ .digest("hex");
499
+ }
500
+ async #assertInstalledExecutable(directory, lock) {
501
+ if (lock.runtime !== "python" || lock.executable.command !== "uv")
502
+ return;
503
+ const executable = join(directory, ".venv", process.platform === "win32" ? "Scripts" : "bin", process.platform === "win32" ? "azure-pricing-mcp.exe" : "azure-pricing-mcp");
504
+ const metadata = await lstat(executable);
505
+ if (!metadata.isFile() || metadata.isSymbolicLink())
506
+ throw new Error("installed capability executable is missing");
507
+ }
508
+ async #assertTreeSafe(root) {
509
+ const rootPath = resolve(root);
510
+ const rootStat = await lstat(rootPath);
511
+ if (rootStat.isSymbolicLink() || !rootStat.isDirectory())
512
+ throw new Error(`Pack root is not a real directory: ${rootPath}`);
513
+ const canonicalRoot = await realpath(rootPath);
514
+ const visit = async (directory) => {
515
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
516
+ const path = join(directory, entry.name);
517
+ const item = await lstat(path);
518
+ if (item.isSymbolicLink())
519
+ throw new Error(`Symlink is not allowed in capability pack: ${relative(rootPath, path)}`);
520
+ const canonical = await realpath(path);
521
+ if (canonical !== canonicalRoot && !canonical.startsWith(`${canonicalRoot}${sep}`))
522
+ throw new Error(`Capability pack path escapes root: ${path}`);
523
+ if (item.isDirectory())
524
+ await visit(path);
525
+ else if (!item.isFile())
526
+ throw new Error(`Unsupported capability pack entry: ${relative(rootPath, path)}`);
527
+ }
528
+ };
529
+ await visit(rootPath);
530
+ }
531
+ async #treeDigest(root, ignored = new Set()) {
532
+ const hash = createHash("sha256");
533
+ const visit = async (directory) => {
534
+ const entries = (await readdir(directory, { withFileTypes: true })).sort((left, right) => compareText(left.name, right.name));
535
+ for (const entry of entries) {
536
+ const path = join(directory, entry.name);
537
+ const name = relative(root, path).split(sep).join("/");
538
+ if ([...ignored].some((ignoredPath) => name === ignoredPath || name.startsWith(`${ignoredPath}/`)))
539
+ continue;
540
+ const item = await lstat(path);
541
+ if (item.isSymbolicLink())
542
+ throw new Error(`Symlink is not allowed in capability pack: ${name}`);
543
+ hash.update(entry.isDirectory() ? `d:${name}\0` : `f:${name}\0`);
544
+ if (entry.isDirectory())
545
+ await visit(path);
546
+ else if (entry.isFile())
547
+ hash.update(await readFile(path));
548
+ else
549
+ throw new Error(`Unsupported capability pack entry: ${name}`);
550
+ }
551
+ };
552
+ await visit(root);
553
+ return hash.digest("hex");
554
+ }
555
+ }
556
+ //# sourceMappingURL=pack-manager.js.map