@sanity/ailf 2.9.0 → 3.0.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 (42) hide show
  1. package/dist/_vendor/ailf-core/artifact-registry.d.ts +1 -1
  2. package/dist/_vendor/ailf-core/artifact-registry.js +1 -18
  3. package/dist/_vendor/ailf-core/batch-signing.d.ts +64 -0
  4. package/dist/_vendor/ailf-core/batch-signing.js +23 -0
  5. package/dist/_vendor/ailf-core/index.d.ts +1 -1
  6. package/dist/_vendor/ailf-core/index.js +1 -1
  7. package/dist/_vendor/ailf-core/ports/context.d.ts +4 -20
  8. package/dist/_vendor/ailf-core/ports/index.d.ts +0 -2
  9. package/dist/adapters/config-sources/file-config-adapter.js +0 -4
  10. package/dist/artifact-capture/redact-artifact.d.ts +3 -5
  11. package/dist/artifact-capture/redact-artifact.js +3 -5
  12. package/dist/cli.js +56 -2
  13. package/dist/commands/explain-handler.js +1 -5
  14. package/dist/commands/pipeline-action.d.ts +0 -4
  15. package/dist/commands/pipeline-action.js +11 -45
  16. package/dist/commands/pipeline.d.ts +1 -5
  17. package/dist/commands/pipeline.js +1 -5
  18. package/dist/commands/runs.d.ts +18 -0
  19. package/dist/commands/runs.js +71 -0
  20. package/dist/composition-root.js +2 -28
  21. package/dist/orchestration/build-app-context.js +4 -7
  22. package/dist/orchestration/pipeline-orchestrator.js +3 -23
  23. package/dist/pipeline/map-request-to-config.js +0 -4
  24. package/package.json +1 -1
  25. package/dist/_vendor/ailf-core/artifact-capture/noop-collector.d.ts +0 -14
  26. package/dist/_vendor/ailf-core/artifact-capture/noop-collector.js +0 -25
  27. package/dist/_vendor/ailf-core/ports/artifact-collector.d.ts +0 -94
  28. package/dist/_vendor/ailf-core/ports/artifact-collector.js +0 -13
  29. package/dist/_vendor/ailf-core/ports/capture-comparator.d.ts +0 -138
  30. package/dist/_vendor/ailf-core/ports/capture-comparator.js +0 -10
  31. package/dist/artifact-capture/comparator.d.ts +0 -22
  32. package/dist/artifact-capture/comparator.js +0 -493
  33. package/dist/artifact-capture/filesystem-collector.d.ts +0 -60
  34. package/dist/artifact-capture/filesystem-collector.js +0 -262
  35. package/dist/artifact-capture/gcs-collector.d.ts +0 -55
  36. package/dist/artifact-capture/gcs-collector.js +0 -117
  37. package/dist/commands/capture-compare.d.ts +0 -15
  38. package/dist/commands/capture-compare.js +0 -253
  39. package/dist/commands/capture-list.d.ts +0 -12
  40. package/dist/commands/capture-list.js +0 -150
  41. package/dist/commands/capture.d.ts +0 -9
  42. package/dist/commands/capture.js +0 -16
@@ -1,262 +0,0 @@
1
- /**
2
- * FilesystemArtifactCollector — DEPRECATED legacy capture path.
3
- *
4
- * W0050 Slice 5: the unified artifact writer (`ctx.artifactWriter.emit()`)
5
- * owns artifact production starting this release. This class is retained
6
- * for one release cycle so producer steps that haven't migrated yet still
7
- * have a surface to call against; W0052 deletes the class + port.
8
- *
9
- * Behavioral changes vs. pre-W0050:
10
- *
11
- * - **No tarball.** The `compress` option is ignored; flush() always writes
12
- * a loose directory tree. The legacy `.tar.gz` artifact archive is no
13
- * longer produced (D0033 AC8 — "no new capture tarballs").
14
- * - **First-call deprecation warning.** The first invocation of capture()
15
- * or captureFile() on any instance emits a one-time-per-process warning
16
- * pointing callers at `ctx.artifactWriter.emit()`.
17
- *
18
- * Everything else (in-memory accumulation, redaction, loose-file output at
19
- * flush time) is unchanged — existing tests continue to pass. This is the
20
- * **minimum** pass-through posture. Full delegation to
21
- * `LocalFilesystemArtifactWriter` is W0052 scope, at which point the port
22
- * and this class are both deleted.
23
- *
24
- * Design principles:
25
- * - capture() and captureFile() are synchronous (no I/O during step execution)
26
- * - flush() does all I/O at pipeline end
27
- * - Failures in capture/captureFile are swallowed (P5: non-blocking)
28
- *
29
- * @deprecated Use `ctx.artifactWriter.emit()` instead; removal scheduled for W0052.
30
- */
31
- import { copyFileSync, mkdirSync, readFileSync, statSync, writeFileSync, } from "node:fs";
32
- import path from "node:path";
33
- import { redactArtifactData } from "./redact-artifact.js";
34
- // ---------------------------------------------------------------------------
35
- // Timestamp-prefixed capture ID (same shape as report-store.ts IDs)
36
- // ---------------------------------------------------------------------------
37
- function generateCaptureId() {
38
- const now = Date.now();
39
- const uuid = crypto.randomUUID();
40
- const hex = now.toString(16).padStart(12, "0");
41
- return (hex.slice(0, 8) +
42
- "-" +
43
- hex.slice(8, 12) +
44
- "-7" +
45
- uuid.slice(15, 18) +
46
- "-" +
47
- uuid.slice(19, 23) +
48
- "-" +
49
- uuid.slice(24));
50
- }
51
- function formatTimestamp(date) {
52
- const y = date.getFullYear();
53
- const m = String(date.getMonth() + 1).padStart(2, "0");
54
- const d = String(date.getDate()).padStart(2, "0");
55
- const h = String(date.getHours()).padStart(2, "0");
56
- const min = String(date.getMinutes()).padStart(2, "0");
57
- const s = String(date.getSeconds()).padStart(2, "0");
58
- return `${y}${m}${d}-${h}${min}${s}`;
59
- }
60
- function detectFormat(entry) {
61
- if (entry.filePath) {
62
- const ext = path.extname(entry.filePath).toLowerCase();
63
- if (ext === ".json")
64
- return "json";
65
- if (ext === ".md")
66
- return "markdown";
67
- if (ext === ".yaml" || ext === ".yml")
68
- return "text";
69
- return "text";
70
- }
71
- // In-memory data — if it's an object/array, it's JSON
72
- if (typeof entry.data === "object" && entry.data !== null)
73
- return "json";
74
- return "text";
75
- }
76
- function fileExtension(format) {
77
- switch (format) {
78
- case "json":
79
- return ".json";
80
- case "markdown":
81
- return ".md";
82
- default:
83
- return ".txt";
84
- }
85
- }
86
- // ---------------------------------------------------------------------------
87
- // Collector
88
- // ---------------------------------------------------------------------------
89
- /**
90
- * Module-level flag so the W0050 deprecation warning fires exactly once
91
- * per process even when multiple collectors are constructed.
92
- */
93
- let warnedLegacyCollector = false;
94
- function warnLegacyCollectorOnce() {
95
- if (warnedLegacyCollector)
96
- return;
97
- warnedLegacyCollector = true;
98
- console.warn("FilesystemArtifactCollector is deprecated (W0050). Producers should migrate to ctx.artifactWriter.emit(); this class will be removed in W0052.");
99
- }
100
- export class FilesystemArtifactCollector {
101
- enabled = true;
102
- extrasEnabled;
103
- entries = [];
104
- captureId;
105
- outputDir;
106
- startedAt;
107
- options;
108
- constructor(options) {
109
- this.options = options;
110
- this.extrasEnabled = options.extras;
111
- this.captureId = generateCaptureId();
112
- this.startedAt = new Date().toISOString();
113
- // Use the last 4 hex chars (from the random UUID portion) — the first
114
- // chars are timestamp-derived and change too slowly to disambiguate.
115
- const shortId = this.captureId.replace(/-/g, "").slice(-4);
116
- const timestamp = formatTimestamp(new Date());
117
- this.outputDir = path.join(options.captureDir, `${options.mode}-${timestamp}-${shortId}`);
118
- }
119
- capture(step, type, data, meta) {
120
- warnLegacyCollectorOnce();
121
- try {
122
- this.entries.push({
123
- step,
124
- type,
125
- data,
126
- meta,
127
- capturedAt: new Date().toISOString(),
128
- });
129
- }
130
- catch {
131
- // P5: non-blocking — swallow capture errors
132
- }
133
- }
134
- captureFile(step, type, filePath, meta) {
135
- warnLegacyCollectorOnce();
136
- try {
137
- this.entries.push({
138
- step,
139
- type,
140
- filePath,
141
- meta,
142
- capturedAt: new Date().toISOString(),
143
- });
144
- }
145
- catch {
146
- // P5: non-blocking — swallow capture errors
147
- }
148
- }
149
- async flush() {
150
- const manifestEntries = [];
151
- let totalBytes = 0;
152
- // Create the root output directory
153
- mkdirSync(this.outputDir, { recursive: true });
154
- for (const entry of this.entries) {
155
- try {
156
- const format = detectFormat(entry);
157
- const ext = entry.filePath
158
- ? path.extname(entry.filePath)
159
- : fileExtension(format);
160
- const relativePath = path.join(entry.step, `${entry.type}${ext}`);
161
- const absolutePath = path.join(this.outputDir, relativePath);
162
- // Ensure step subdirectory exists
163
- mkdirSync(path.dirname(absolutePath), { recursive: true });
164
- // Write the artifact — JSON artifacts are redacted before persistence
165
- let bytes;
166
- if (entry.filePath && format === "json") {
167
- // JSON file on disk: read → parse → redact → write
168
- const raw = readFileSync(entry.filePath, "utf-8");
169
- try {
170
- const parsed = JSON.parse(raw);
171
- const redacted = redactArtifactData(parsed);
172
- const content = JSON.stringify(redacted, null, 2);
173
- writeFileSync(absolutePath, content, "utf-8");
174
- bytes = Buffer.byteLength(content, "utf-8");
175
- }
176
- catch {
177
- // Unparseable JSON — copy as-is (comparator will scan it later)
178
- copyFileSync(entry.filePath, absolutePath);
179
- bytes = statSync(absolutePath).size;
180
- }
181
- }
182
- else if (entry.filePath) {
183
- // Non-JSON file: copy as-is
184
- copyFileSync(entry.filePath, absolutePath);
185
- bytes = statSync(absolutePath).size;
186
- }
187
- else {
188
- let content;
189
- if (format === "json") {
190
- try {
191
- content = JSON.stringify(redactArtifactData(entry.data), null, 2);
192
- }
193
- catch {
194
- // Fallback: serialize unredacted (e.g., circular references)
195
- content = JSON.stringify(entry.data, null, 2);
196
- }
197
- }
198
- else {
199
- content = String(entry.data ?? "");
200
- }
201
- writeFileSync(absolutePath, content, "utf-8");
202
- bytes = Buffer.byteLength(content, "utf-8");
203
- }
204
- totalBytes += bytes;
205
- manifestEntries.push({
206
- step: entry.step,
207
- type: entry.type,
208
- path: relativePath,
209
- capturedAt: entry.capturedAt,
210
- bytes,
211
- format,
212
- ...(entry.meta ? { meta: entry.meta } : {}),
213
- });
214
- }
215
- catch {
216
- // P5: skip individual artifact failures, continue with the rest
217
- }
218
- }
219
- // Write manifest
220
- const manifest = {
221
- version: 1,
222
- captureId: this.captureId,
223
- startedAt: this.startedAt,
224
- completedAt: new Date().toISOString(),
225
- pipeline: {
226
- mode: this.options.mode,
227
- ...(this.options.pipeline?.variant
228
- ? { variant: this.options.pipeline.variant }
229
- : {}),
230
- ...(this.options.pipeline?.source
231
- ? { source: this.options.pipeline.source }
232
- : {}),
233
- ...(this.options.pipeline?.areas
234
- ? { areas: this.options.pipeline.areas }
235
- : {}),
236
- },
237
- artifacts: manifestEntries,
238
- };
239
- const manifestContent = JSON.stringify(manifest, null, 2);
240
- writeFileSync(path.join(this.outputDir, "manifest.json"), manifestContent, "utf-8");
241
- // W0050 Slice 5 — compression disabled unconditionally. The legacy
242
- // .tar.gz archive is no longer produced (D0033 AC8 "no new capture
243
- // tarballs"); callers that asked for it get the loose directory plus
244
- // a one-time warning.
245
- if (this.options.compress) {
246
- warnTarballSuppressedOnce();
247
- }
248
- return {
249
- artifactCount: manifestEntries.length,
250
- destination: this.outputDir,
251
- totalBytes,
252
- compressed: false,
253
- };
254
- }
255
- }
256
- let warnedTarballSuppressed = false;
257
- function warnTarballSuppressedOnce() {
258
- if (warnedTarballSuppressed)
259
- return;
260
- warnedTarballSuppressed = true;
261
- console.warn("capture.compress is deprecated (W0050); tarball output is no longer produced. The loose directory at <captureDir>/<run>-<ts>-<id>/ contains the same files.");
262
- }
@@ -1,55 +0,0 @@
1
- /**
2
- * GcsArtifactCollector — decorator that uploads capture artifacts to GCS.
3
- *
4
- * Wraps the FilesystemArtifactCollector: local flush first (preserving
5
- * the existing manifest + redaction logic), then upload to a GCS bucket.
6
- *
7
- * Design principles:
8
- * - P5: Non-blocking — GCS upload failure should not block the pipeline.
9
- * Local artifacts are always preserved.
10
- * - Decorator pattern — delegates capture() and captureFile() to the inner
11
- * collector unchanged. Only flush() adds the GCS upload step.
12
- * - Lazy client — GCS Storage client is created on first flush(), not at
13
- * construction (same pattern as BigQuerySink).
14
- *
15
- * @see docs/decisions/D0030-external-artifact-store.md
16
- * @see docs/work-items/W0035-gcs-artifact-output.json
17
- */
18
- import type { ArtifactCollector, CaptureFlushResult } from "../_vendor/ailf-core/index.d.ts";
19
- export interface GcsCollectorOptions {
20
- /** GCS bucket name (e.g., "ailf-artifacts") */
21
- bucket: string;
22
- /** Object prefix in the bucket (e.g., "captures/") */
23
- prefix?: string;
24
- /** Path to service account credentials JSON (optional — falls back to ADC) */
25
- credentials?: string;
26
- }
27
- export interface GcsFlushResult extends CaptureFlushResult {
28
- /** GCS upload status */
29
- gcs: {
30
- status: "uploaded";
31
- bucket: string;
32
- path: string;
33
- } | {
34
- status: "skipped";
35
- reason: string;
36
- } | {
37
- status: "failed";
38
- error: string;
39
- };
40
- }
41
- export declare class GcsArtifactCollector implements ArtifactCollector {
42
- get enabled(): boolean;
43
- get extrasEnabled(): boolean;
44
- private client;
45
- private readonly inner;
46
- private readonly options;
47
- constructor(inner: ArtifactCollector, options: GcsCollectorOptions);
48
- capture(step: string, type: string, data: unknown, meta?: Record<string, unknown>): void;
49
- captureFile(step: string, type: string, filePath: string, meta?: Record<string, unknown>): void;
50
- flush(): Promise<GcsFlushResult>;
51
- /** Lazily create the GCS Storage client. */
52
- private getClient;
53
- /** Upload the flushed artifact (tar.gz or directory) to GCS. */
54
- private uploadToGcs;
55
- }
@@ -1,117 +0,0 @@
1
- /**
2
- * GcsArtifactCollector — decorator that uploads capture artifacts to GCS.
3
- *
4
- * Wraps the FilesystemArtifactCollector: local flush first (preserving
5
- * the existing manifest + redaction logic), then upload to a GCS bucket.
6
- *
7
- * Design principles:
8
- * - P5: Non-blocking — GCS upload failure should not block the pipeline.
9
- * Local artifacts are always preserved.
10
- * - Decorator pattern — delegates capture() and captureFile() to the inner
11
- * collector unchanged. Only flush() adds the GCS upload step.
12
- * - Lazy client — GCS Storage client is created on first flush(), not at
13
- * construction (same pattern as BigQuerySink).
14
- *
15
- * @see docs/decisions/D0030-external-artifact-store.md
16
- * @see docs/work-items/W0035-gcs-artifact-output.json
17
- */
18
- import { readFileSync } from "node:fs";
19
- import { Storage } from "@google-cloud/storage";
20
- // ---------------------------------------------------------------------------
21
- // Collector
22
- // ---------------------------------------------------------------------------
23
- export class GcsArtifactCollector {
24
- get enabled() {
25
- return this.inner.enabled;
26
- }
27
- get extrasEnabled() {
28
- return this.inner.extrasEnabled;
29
- }
30
- client = null;
31
- inner;
32
- options;
33
- constructor(inner, options) {
34
- this.inner = inner;
35
- this.options = options;
36
- }
37
- capture(step, type, data, meta) {
38
- this.inner.capture(step, type, data, meta);
39
- }
40
- captureFile(step, type, filePath, meta) {
41
- this.inner.captureFile(step, type, filePath, meta);
42
- }
43
- async flush() {
44
- // Step 1: Flush to local filesystem first (always succeeds or throws)
45
- const localResult = await this.inner.flush();
46
- // Step 2: Upload to GCS (non-blocking — P5)
47
- if (localResult.artifactCount === 0) {
48
- return {
49
- ...localResult,
50
- gcs: { status: "skipped", reason: "No artifacts to upload" },
51
- };
52
- }
53
- try {
54
- const gcsPath = await this.uploadToGcs(localResult);
55
- return {
56
- ...localResult,
57
- gcs: {
58
- status: "uploaded",
59
- bucket: this.options.bucket,
60
- path: gcsPath,
61
- },
62
- };
63
- }
64
- catch (err) {
65
- const message = err instanceof Error ? err.message : String(err);
66
- console.warn(` ⚠️ GCS upload failed (non-blocking): ${message}`);
67
- return {
68
- ...localResult,
69
- gcs: { status: "failed", error: message },
70
- };
71
- }
72
- }
73
- // -----------------------------------------------------------------------
74
- // Private helpers
75
- // -----------------------------------------------------------------------
76
- /** Lazily create the GCS Storage client. */
77
- getClient() {
78
- if (this.client)
79
- return this.client;
80
- this.client = this.options.credentials
81
- ? new Storage({ keyFilename: this.options.credentials })
82
- : new Storage();
83
- return this.client;
84
- }
85
- /** Upload the flushed artifact (tar.gz or directory) to GCS. */
86
- async uploadToGcs(result) {
87
- const storage = this.getClient();
88
- const bucket = storage.bucket(this.options.bucket);
89
- const prefix = this.options.prefix ?? "captures/";
90
- if (result.compressed) {
91
- // Upload the tar.gz directly
92
- const fileName = result.destination.split("/").pop() ?? "capture.tar.gz";
93
- const gcsPath = `${prefix}${fileName}`;
94
- const fileContent = readFileSync(result.destination);
95
- await bucket.file(gcsPath).save(fileContent, {
96
- contentType: "application/gzip",
97
- metadata: {
98
- artifactCount: String(result.artifactCount),
99
- totalBytes: String(result.totalBytes),
100
- },
101
- });
102
- return gcsPath;
103
- }
104
- // Uncompressed: upload the manifest.json as the representative file.
105
- // The full directory could be uploaded file-by-file, but for the
106
- // capture use case (forensic archive), the compressed bundle is the
107
- // expected path. Upload just the manifest as a reference.
108
- const manifestPath = `${result.destination}/manifest.json`;
109
- const dirName = result.destination.split("/").pop() ?? "capture";
110
- const gcsPath = `${prefix}${dirName}/manifest.json`;
111
- const manifestContent = readFileSync(manifestPath, "utf-8");
112
- await bucket.file(gcsPath).save(manifestContent, {
113
- contentType: "application/json",
114
- });
115
- return gcsPath;
116
- }
117
- }
@@ -1,15 +0,0 @@
1
- /**
2
- * capture compare — compare two pipeline capture directories.
3
- *
4
- * Reads manifest.json from both captures, runs compareCaptures(),
5
- * and prints a human-readable table or JSON diff report.
6
- *
7
- * Supports both raw directories and .tar.gz archives.
8
- *
9
- * Exit codes:
10
- * 0 — captures are equivalent
11
- * 1 — differences found
12
- * 2 — error (missing files, invalid manifest, etc.)
13
- */
14
- import { Command } from "commander";
15
- export declare function createCaptureCompareCommand(): Command;