akm-cli 0.9.15 → 0.9.16-alpha.2
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.
- package/CHANGELOG.md +52 -0
- package/dist/assets/hints/cli-hints-full.md +13 -6
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +3 -6
- package/dist/cli/retired-commands.js +0 -2
- package/dist/commands/env/env-binding.js +4 -4
- package/dist/commands/env/env-cli.js +3 -3
- package/dist/commands/improve/improve-cli.js +19 -14
- package/dist/commands/improve/reflect.js +23 -2
- package/dist/commands/lint/base-linter.js +9 -0
- package/dist/commands/lint/env-key-rules.js +2 -2
- package/dist/commands/proposal/propose.js +15 -1
- package/dist/commands/proposal/validators/proposal-quality-validators.js +40 -3
- package/dist/commands/proposal/validators/proposal-validators.js +5 -4
- package/dist/commands/read/search.js +33 -4
- package/dist/commands/read/show.js +21 -2
- package/dist/commands/registry-cli.js +5 -5
- package/dist/commands/sources/add-cli.js +59 -16
- package/dist/commands/sources/bundle-cli.js +35 -11
- package/dist/commands/sources/bundle-config-ops.js +30 -0
- package/dist/commands/sources/dangerous-env-audit.js +4 -4
- package/dist/commands/sources/installed-stashes.js +43 -28
- package/dist/commands/sources/source-add.js +33 -17
- package/dist/commands/sources/source-manage.js +34 -12
- package/dist/commands/sources/stash-skeleton.js +6 -3
- package/dist/commands/tasks/explain.js +4 -1
- package/dist/commands/tasks/tasks-cli.js +31 -9
- package/dist/commands/tasks/tasks.js +239 -194
- package/dist/commands/tasks/validate.js +20 -32
- package/dist/core/activation-policy.js +4 -4
- package/dist/core/adapter/adapters/akm-adapter.js +5 -0
- package/dist/core/adapter/execution-source.js +10 -29
- package/dist/core/config/config-schema.js +64 -8
- package/dist/core/config/config-sources.js +96 -2
- package/dist/core/config/config.js +190 -24
- package/dist/core/config/legacy-source-shape-shim.js +9 -0
- package/dist/core/config/schema/execution.js +23 -0
- package/dist/core/config/schema/experimental.js +1 -1
- package/dist/core/config/schema/scheduler.js +20 -0
- package/dist/core/config/schema/search.js +1 -1
- package/dist/core/config/schema/sources-bundles.js +32 -1
- package/dist/core/content-safety.js +52 -0
- package/dist/core/maintenance-barrier.js +6 -6
- package/dist/core/type-presentation.js +1 -1
- package/dist/core/write-source.js +13 -8
- package/dist/indexer/bundle-identity-guard.js +45 -8
- package/dist/indexer/indexer.js +1 -1
- package/dist/indexer/materialize-embeddings.js +15 -1
- package/dist/indexer/search/search-source.js +29 -11
- package/dist/integrations/agent/execution-lowering.js +3 -2
- package/dist/integrations/agent/execution-preparation.js +32 -1
- package/dist/integrations/agent/prompts.js +1 -1
- package/dist/integrations/agent/request-lowering.js +3 -2
- package/dist/llm/client.js +2 -1
- package/dist/output/shapes/passthrough.js +2 -0
- package/dist/registry/resolve.js +37 -10
- package/dist/scripts/akm-migrate-node.js +13644 -9894
- package/dist/scripts/akm-migrate.js +12626 -8876
- package/dist/setup/setup.js +3 -3
- package/dist/setup/steps/tasks.js +29 -36
- package/dist/sources/providers/git-install.js +17 -11
- package/dist/sources/providers/git-provider.js +12 -5
- package/dist/sources/providers/git-stash.js +38 -16
- package/dist/sources/snapshot-fetchers/website-ingest.js +3 -3
- package/dist/storage/repositories/index-vec-repository.js +100 -0
- package/dist/tasks/activation-config.js +90 -0
- package/dist/tasks/backends/cron.js +9 -0
- package/dist/tasks/backends/launchd.js +1 -0
- package/dist/tasks/backends/schtasks.js +2 -0
- package/dist/tasks/embedded.js +4 -5
- package/dist/tasks/scheduler-binding.js +2 -2
- package/dist/tasks/scheduler-sync-preview.js +8 -1
- package/dist/tasks/scheduler-sync.js +19 -10
- package/dist/tasks/source/parse-task-source.js +10 -113
- package/dist/tasks/source/project-v4.js +2 -2
- package/dist/tasks/source/task-source-v4.js +4 -12
- package/dist/tasks/source/task-to-v3.js +4 -12
- package/dist/tasks/source/task-to-v4.js +40 -7
- package/docs/migration/README.md +1 -0
- package/docs/migration/release-notes/0.9.16.md +72 -0
- package/docs/migration/v0.9.1-to-v0.9.2.md +6 -9
- package/docs/reference/cli.md +37 -29
- package/docs/reference/configuration.md +48 -5
- package/docs/reference/tasks.md +34 -29
- package/package.json +1 -1
- package/schemas/akm-config.json +112 -4
- package/schemas/akm-task.json +1 -2
package/dist/setup/setup.js
CHANGED
|
@@ -205,8 +205,8 @@ export function rebaseSetupChanges(original, desired, latest, pathParts = []) {
|
|
|
205
205
|
}
|
|
206
206
|
return result;
|
|
207
207
|
}
|
|
208
|
-
async function saveSetupConfig(original, desired, precommit) {
|
|
209
|
-
const result = await mutateConfigWithPrecommit((latest) => rebaseSetupChanges(original, desired, latest), precommit);
|
|
208
|
+
async function saveSetupConfig(original, desired, precommit, options) {
|
|
209
|
+
const result = await mutateConfigWithPrecommit((latest) => rebaseSetupChanges(original, desired, latest), precommit, options);
|
|
210
210
|
return { config: result.config, precommit: result.precommit };
|
|
211
211
|
}
|
|
212
212
|
/** Registry-managed bundles are not editable through setup's source picker. */
|
|
@@ -572,7 +572,7 @@ export async function runSetupWizard(opts) {
|
|
|
572
572
|
const { config: savedConfig } = await saveSetupConfig(current, finalConfig, async () => {
|
|
573
573
|
if (!opts?.noInit)
|
|
574
574
|
await akmInit({ dir: stashDir, setDefault: true, persistConfig: false });
|
|
575
|
-
});
|
|
575
|
+
}, { persistTopLevelKeys: ["semanticSearchMode"] });
|
|
576
576
|
// After config persistence, the task step reviews the plan and asks one
|
|
577
577
|
// explicit confirmation before changing task files or scheduler state.
|
|
578
578
|
// Non-interactive setup paths never reach this interactive-only step.
|
|
@@ -8,9 +8,11 @@ import path from "node:path";
|
|
|
8
8
|
import { parse as yamlParse, stringify as yamlStringify } from "yaml";
|
|
9
9
|
import * as p from "../../cli/clack.js";
|
|
10
10
|
import { akmTasksSync } from "../../commands/tasks/tasks.js";
|
|
11
|
-
import {
|
|
11
|
+
import { makeBundleRef } from "../../core/asset/asset-ref.js";
|
|
12
|
+
import { loadConfig, mutateConfig } from "../../core/config/config.js";
|
|
12
13
|
import { UsageError } from "../../core/errors.js";
|
|
13
14
|
import { commitWriteTargetBoundary, deleteAssetFromSource, prepareWriteTargetForMutation, resolveWriteTarget, writeAssetToSource, } from "../../core/write-source.js";
|
|
15
|
+
import { schedulerActivationSourceId, schedulerActivations } from "../../tasks/activation-config.js";
|
|
14
16
|
import { backendNameForPlatform } from "../../tasks/backends/index.js";
|
|
15
17
|
import { listEmbeddedTasks } from "../../tasks/embedded.js";
|
|
16
18
|
import { parseSchedule } from "../../tasks/schedule.js";
|
|
@@ -71,34 +73,13 @@ export function detectServerDefault() {
|
|
|
71
73
|
function normaliseTaskIdForMatch(raw) {
|
|
72
74
|
return raw.trim().replace(/\.(yml|md)$/, "");
|
|
73
75
|
}
|
|
74
|
-
/**
|
|
75
|
-
* Toggle a task source v4 file's enabled state via a full parse/render
|
|
76
|
-
* round-trip (setup's own edits are infrequent and not comment-preservation-
|
|
77
|
-
* sensitive, unlike `commands/tasks/tasks.ts`'s `setEnabledInYaml` line
|
|
78
|
-
* splice). Broadcasts `enabled` across every `schedule[]` entry — the
|
|
79
|
-
* closest v4 equivalent of v3's single document-level flag. `src` no longer
|
|
80
|
-
* accepts a task v3 file at all (P4 §3.2) — `listSetupTaskDefinitions` below
|
|
81
|
-
* already fails closed on one before this function is ever reached, so
|
|
82
|
-
* there is no legacy `akm.enabled` shape left to handle here.
|
|
83
|
-
*/
|
|
84
|
-
function setTaskEnabledInYaml(yaml, enabled) {
|
|
85
|
-
const document = yamlParse(yaml);
|
|
86
|
-
const schedule = document.schedule;
|
|
87
|
-
if (typeof schedule === "string") {
|
|
88
|
-
document.schedule = [{ cron: schedule, enabled }];
|
|
89
|
-
}
|
|
90
|
-
else if (Array.isArray(schedule) && schedule.length > 0) {
|
|
91
|
-
document.schedule = schedule.map((entry) => entry && typeof entry === "object" && !Array.isArray(entry) ? { ...entry, enabled } : entry);
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
throw new UsageError("Task source v4 must declare a schedule before setup can change enabled state.");
|
|
95
|
-
}
|
|
96
|
-
return yamlStringify(document);
|
|
97
|
-
}
|
|
98
76
|
export function listSetupTaskDefinitions() {
|
|
99
77
|
const config = loadConfig();
|
|
100
78
|
const target = resolveWriteTarget(config, config.defaultBundle, { requireWritable: false });
|
|
101
79
|
const taskDir = path.join(target.source.path, "tasks");
|
|
80
|
+
const enabledRefs = new Set(schedulerActivations(config)
|
|
81
|
+
.filter((activation) => activation.kind === "task")
|
|
82
|
+
.map((activation) => activation.ref));
|
|
102
83
|
if (!fs.existsSync(taskDir))
|
|
103
84
|
return [];
|
|
104
85
|
const tasks = [];
|
|
@@ -121,10 +102,7 @@ export function listSetupTaskDefinitions() {
|
|
|
121
102
|
id,
|
|
122
103
|
schedule: schedules[0],
|
|
123
104
|
schedules: Object.freeze(schedules),
|
|
124
|
-
|
|
125
|
-
// considered enabled for review purposes when at least one of its
|
|
126
|
-
// schedule bindings will actually fire.
|
|
127
|
-
enabled: document.schedule.some((entry) => entry.enabled),
|
|
105
|
+
enabled: enabledRefs.has(makeBundleRef(target.source.name, `tasks/${id}`)),
|
|
128
106
|
...(document.description !== undefined ? { description: document.description } : {}),
|
|
129
107
|
});
|
|
130
108
|
}
|
|
@@ -147,11 +125,11 @@ export async function prepareSetupTaskDefinitions(tasks, deps = {}) {
|
|
|
147
125
|
const original = fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8") : undefined;
|
|
148
126
|
let yaml;
|
|
149
127
|
if (original !== undefined) {
|
|
150
|
-
yaml =
|
|
128
|
+
yaml = original;
|
|
151
129
|
}
|
|
152
130
|
else {
|
|
153
131
|
const document = yamlParse(plan.task.yaml);
|
|
154
|
-
document.schedule = plan.
|
|
132
|
+
document.schedule = plan.schedule;
|
|
155
133
|
yaml = yamlStringify(document);
|
|
156
134
|
}
|
|
157
135
|
const parsed = parseTaskSource({ yaml, filePath, workspaceRoot: target.source.path });
|
|
@@ -161,8 +139,6 @@ export async function prepareSetupTaskDefinitions(tasks, deps = {}) {
|
|
|
161
139
|
return { filePath, original, yaml, ref: { type: "task", name: plan.task.id } };
|
|
162
140
|
});
|
|
163
141
|
const changed = prepared.filter((entry) => entry.original !== entry.yaml);
|
|
164
|
-
if (changed.length === 0)
|
|
165
|
-
return 0;
|
|
166
142
|
const attempted = [];
|
|
167
143
|
try {
|
|
168
144
|
for (const entry of changed) {
|
|
@@ -199,6 +175,23 @@ export async function prepareSetupTaskDefinitions(tasks, deps = {}) {
|
|
|
199
175
|
}
|
|
200
176
|
throw error;
|
|
201
177
|
}
|
|
178
|
+
const selected = new Set(tasks.filter((plan) => plan.enabled).map((plan) => makeBundleRef(target.source.name, `tasks/${plan.task.id}`)));
|
|
179
|
+
const managed = new Set(tasks.map((plan) => makeBundleRef(target.source.name, `tasks/${plan.task.id}`)));
|
|
180
|
+
mutateConfig((current) => {
|
|
181
|
+
const existing = schedulerActivations(current);
|
|
182
|
+
const next = existing.filter((activation) => activation.kind !== "task" || !managed.has(activation.ref));
|
|
183
|
+
const sourceId = schedulerActivationSourceId(current, target.source.name);
|
|
184
|
+
if (selected.size > 0 && !sourceId) {
|
|
185
|
+
throw new UsageError(`Cannot activate setup tasks from disabled bundle ${JSON.stringify(target.source.name)}.`);
|
|
186
|
+
}
|
|
187
|
+
for (const ref of selected) {
|
|
188
|
+
next.push({ kind: "task", ref, sourceId: sourceId });
|
|
189
|
+
}
|
|
190
|
+
next.sort((left, right) => left.ref.localeCompare(right.ref) || left.kind.localeCompare(right.kind));
|
|
191
|
+
if (JSON.stringify(existing) === JSON.stringify(next))
|
|
192
|
+
return current;
|
|
193
|
+
return { ...current, scheduler: { ...current.scheduler, enabled: next } };
|
|
194
|
+
});
|
|
202
195
|
return changed.length;
|
|
203
196
|
}
|
|
204
197
|
const DEFAULT_SCHEDULED_TASKS_DEPS = {
|
|
@@ -212,9 +205,9 @@ export async function stepScheduledTasks(deps = DEFAULT_SCHEDULED_TASKS_DEPS, op
|
|
|
212
205
|
return;
|
|
213
206
|
}
|
|
214
207
|
// ALL templates are offered, including ships-disabled ones (e.g. the
|
|
215
|
-
// manual-recovery catchup task): an unselected template is still PREPARED
|
|
216
|
-
//
|
|
217
|
-
//
|
|
208
|
+
// manual-recovery catchup task): an unselected template is still PREPARED,
|
|
209
|
+
// so its YAML exists for `akm task run <id>` while its ref remains absent
|
|
210
|
+
// from local scheduler activation. Filtering on `task.enabled`
|
|
218
211
|
// here would make ships-disabled templates invisible and unpreparable.
|
|
219
212
|
const embedded = listEmbeddedTasks();
|
|
220
213
|
if (embedded.length === 0)
|
|
@@ -8,7 +8,7 @@ import path from "node:path";
|
|
|
8
8
|
import { isWithin } from "../../core/common.js";
|
|
9
9
|
import { UsageError } from "../../core/errors.js";
|
|
10
10
|
import { getRegistryCacheDir } from "../../core/paths.js";
|
|
11
|
-
import { parseRegistryRef, resolveRegistryArtifact, validateGitRef, validateGitUrl } from "../../registry/resolve.js";
|
|
11
|
+
import { gitCredentialEnvironment, parseRegistryRef, resolveRegistryArtifact, validateGitRef, validateGitUrl, } from "../../registry/resolve.js";
|
|
12
12
|
import { applyAkmIncludeConfig, buildInstallCacheDir, detectStashRoot, isDirectory } from "./provider-utils.js";
|
|
13
13
|
/**
|
|
14
14
|
* Shared subprocess wrapper for `git` invocations. Disables git's interactive
|
|
@@ -22,13 +22,16 @@ export function runGit(args, options) {
|
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
24
|
/** Fetch and classify the current branch against its upstream without changing the worktree. */
|
|
25
|
-
export function inspectGitUpstream(repoDir) {
|
|
25
|
+
export function inspectGitUpstream(repoDir, credential) {
|
|
26
26
|
const remotes = runGit(["-C", repoDir, "remote"]);
|
|
27
27
|
if (remotes.status !== 0)
|
|
28
28
|
throw new UsageError(`Cannot inspect Git remotes at ${repoDir}: ${remotes.stderr.trim()}`);
|
|
29
29
|
if (!remotes.stdout.trim())
|
|
30
30
|
return { hasRemote: false, ahead: 0, behind: 0 };
|
|
31
|
-
const fetch = runGit(["-C", repoDir, "fetch", "--prune"], {
|
|
31
|
+
const fetch = runGit(["-C", repoDir, "fetch", "--prune"], {
|
|
32
|
+
timeout: 120_000,
|
|
33
|
+
env: gitCredentialEnvironment(credential),
|
|
34
|
+
});
|
|
32
35
|
if (fetch.status !== 0)
|
|
33
36
|
throw new UsageError(`Cannot refresh Git target at ${repoDir}: ${fetch.stderr.trim()}`);
|
|
34
37
|
const upstream = runGit(["-C", repoDir, "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]);
|
|
@@ -160,10 +163,10 @@ async function doSyncGit(parsed, options) {
|
|
|
160
163
|
validateGitUrl(parsed.url);
|
|
161
164
|
if (parsed.requestedRef)
|
|
162
165
|
validateGitRef(parsed.requestedRef);
|
|
163
|
-
const resolved = await resolveRegistryArtifact(parsed);
|
|
166
|
+
const resolved = await resolveRegistryArtifact(parsed, { gitCredential: options?.credential });
|
|
164
167
|
const syncedAt = (options?.now ?? new Date()).toISOString();
|
|
165
168
|
if (options?.writable && options.writableRoot) {
|
|
166
|
-
return syncExistingWritableCheckout(parsed, resolved, options.writableRoot, syncedAt, options.writableRequiredRoots);
|
|
169
|
+
return syncExistingWritableCheckout(parsed, resolved, options.writableRoot, syncedAt, options.writableRequiredRoots, options.credential);
|
|
167
170
|
}
|
|
168
171
|
const cacheRootDir = options?.cacheRootDir ?? getRegistryCacheDir();
|
|
169
172
|
const cacheDir = buildInstallCacheDir(cacheRootDir, parsed.source, parsed.id, options?.writable ? "writable" : resolved.resolvedRevision);
|
|
@@ -178,7 +181,7 @@ async function doSyncGit(parsed, options) {
|
|
|
178
181
|
if (installRoot !== provisionalBundleRoot) {
|
|
179
182
|
throw new UsageError("Writable Git installs do not support akm.include (package.json) filtered snapshots.");
|
|
180
183
|
}
|
|
181
|
-
return syncExistingWritableCheckout(parsed, resolved, detectStashRoot(installRoot), syncedAt, options.writableRequiredRoots);
|
|
184
|
+
return syncExistingWritableCheckout(parsed, resolved, detectStashRoot(installRoot), syncedAt, options.writableRequiredRoots, options.credential);
|
|
182
185
|
}
|
|
183
186
|
try {
|
|
184
187
|
if (options?.force) {
|
|
@@ -228,7 +231,10 @@ async function doSyncGit(parsed, options) {
|
|
|
228
231
|
cloneArgs.push("--branch", parsed.requestedRef);
|
|
229
232
|
}
|
|
230
233
|
cloneArgs.push(parsed.url, cloneDir);
|
|
231
|
-
const cloneResult = runGit(cloneArgs, {
|
|
234
|
+
const cloneResult = runGit(cloneArgs, {
|
|
235
|
+
timeout: 120_000,
|
|
236
|
+
env: gitCredentialEnvironment(options?.credential),
|
|
237
|
+
});
|
|
232
238
|
if (cloneResult.status !== 0) {
|
|
233
239
|
throw new Error(classifyCloneFailure(parsed.url, cloneResult.stderr, cloneResult.error));
|
|
234
240
|
}
|
|
@@ -292,7 +298,7 @@ async function doSyncGit(parsed, options) {
|
|
|
292
298
|
syncedAt,
|
|
293
299
|
};
|
|
294
300
|
}
|
|
295
|
-
export function syncExistingWritableCheckout(parsed, resolved, contentRoot, syncedAt, requiredRoots = []) {
|
|
301
|
+
export function syncExistingWritableCheckout(parsed, resolved, contentRoot, syncedAt, requiredRoots = [], credential) {
|
|
296
302
|
const root = path.resolve(contentRoot);
|
|
297
303
|
const repoResult = runGit(["-C", root, "rev-parse", "--show-toplevel"]);
|
|
298
304
|
if (repoResult.status !== 0 || !repoResult.stdout.trim()) {
|
|
@@ -324,7 +330,7 @@ export function syncExistingWritableCheckout(parsed, resolved, contentRoot, sync
|
|
|
324
330
|
fetchArgs.push("origin");
|
|
325
331
|
if (parsed.requestedRef)
|
|
326
332
|
fetchArgs.push(parsed.requestedRef);
|
|
327
|
-
const fetch = runGit(fetchArgs, { timeout: 120_000 });
|
|
333
|
+
const fetch = runGit(fetchArgs, { timeout: 120_000, env: gitCredentialEnvironment(credential) });
|
|
328
334
|
if (fetch.status !== 0) {
|
|
329
335
|
throw new UsageError(`Writable Git install at ${root} could not fetch its expected origin; local work was preserved. ${fetch.stderr.trim()}`);
|
|
330
336
|
}
|
|
@@ -403,7 +409,7 @@ export function syncExistingWritableCheckout(parsed, resolved, contentRoot, sync
|
|
|
403
409
|
syncedAt,
|
|
404
410
|
};
|
|
405
411
|
}
|
|
406
|
-
export function cloneRepo(cloneUrl, ref, destDir, writable = false) {
|
|
412
|
+
export function cloneRepo(cloneUrl, ref, destDir, writable = false, credential) {
|
|
407
413
|
// Stage the clone into a sibling temp dir so that a failed clone never
|
|
408
414
|
// destroys a previously-valid destDir (e.g. when the remote is temporarily
|
|
409
415
|
// unreachable and we have a valid cached copy).
|
|
@@ -414,7 +420,7 @@ export function cloneRepo(cloneUrl, ref, destDir, writable = false) {
|
|
|
414
420
|
if (ref)
|
|
415
421
|
args.push("--branch", ref);
|
|
416
422
|
args.push(cloneUrl, tmpDir);
|
|
417
|
-
const result = runGit(args, { timeout: 120_000 });
|
|
423
|
+
const result = runGit(args, { timeout: 120_000, env: gitCredentialEnvironment(credential) });
|
|
418
424
|
if (result.status !== 0) {
|
|
419
425
|
// Clean up the (possibly partial) temp dir but leave destDir untouched.
|
|
420
426
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
@@ -6,6 +6,7 @@ import fs from "node:fs";
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { akmAdapter } from "../../core/adapter/adapters/akm-adapter.js";
|
|
8
8
|
import { stashDirNames } from "../../core/asset/asset-placement.js";
|
|
9
|
+
import { resolveSecret } from "../../core/config/config.js";
|
|
9
10
|
import { ConfigError, UsageError } from "../../core/errors.js";
|
|
10
11
|
import { getRegistryIndexCacheDir } from "../../core/paths.js";
|
|
11
12
|
import { validateGitUrl } from "../../registry/resolve.js";
|
|
@@ -41,7 +42,12 @@ export class GitSourceProvider {
|
|
|
41
42
|
return this.#path;
|
|
42
43
|
}
|
|
43
44
|
async sync(options) {
|
|
44
|
-
await syncMirroredRepo(this.#config, {
|
|
45
|
+
await syncMirroredRepo(this.#config, {
|
|
46
|
+
force: options?.force,
|
|
47
|
+
...(this.#config.credential
|
|
48
|
+
? { credential: resolveSecret(this.#config.credential, options?.secrets?.resolveSecret) }
|
|
49
|
+
: {}),
|
|
50
|
+
});
|
|
45
51
|
}
|
|
46
52
|
}
|
|
47
53
|
/** Resolve the on-disk content directory for a configured git source. */
|
|
@@ -81,10 +87,10 @@ export async function ensureGitMirror(repo, cachePaths, options) {
|
|
|
81
87
|
fs.mkdirSync(cachePaths.rootDir, { recursive: true });
|
|
82
88
|
if (writable && fs.existsSync(path.join(cachePaths.repoDir, ".git"))) {
|
|
83
89
|
// Writable repo already cloned — pull instead of re-clone to preserve local changes
|
|
84
|
-
pullRepo(cachePaths.repoDir);
|
|
90
|
+
pullRepo(cachePaths.repoDir, options?.credential);
|
|
85
91
|
}
|
|
86
92
|
else {
|
|
87
|
-
cloneRepo(repo.cloneUrl, repo.ref, cachePaths.repoDir, writable);
|
|
93
|
+
cloneRepo(repo.cloneUrl, repo.ref, cachePaths.repoDir, writable, options?.credential);
|
|
88
94
|
}
|
|
89
95
|
// Touch index file to track freshness
|
|
90
96
|
fs.writeFileSync(cachePaths.indexPath, "[]", { encoding: "utf8", mode: 0o600 });
|
|
@@ -106,6 +112,7 @@ export async function syncMirroredRepo(config, options) {
|
|
|
106
112
|
requireRepoDir: true,
|
|
107
113
|
writable: options?.writable ?? config.writable === true,
|
|
108
114
|
force: options?.force,
|
|
115
|
+
credential: options?.credential,
|
|
109
116
|
});
|
|
110
117
|
const syncedAt = (options?.now ?? new Date()).toISOString();
|
|
111
118
|
const contentDir = cachePaths.repoDir;
|
|
@@ -121,12 +128,12 @@ export async function syncMirroredRepo(config, options) {
|
|
|
121
128
|
syncedAt,
|
|
122
129
|
};
|
|
123
130
|
}
|
|
124
|
-
function pullRepo(repoDir) {
|
|
131
|
+
function pullRepo(repoDir, credential) {
|
|
125
132
|
const status = runGit(["-C", repoDir, "status", "--porcelain"]);
|
|
126
133
|
if (status.status !== 0 || status.stdout.trim()) {
|
|
127
134
|
throw new UsageError(`Writable Git source at ${repoDir} has uncommitted changes; refusing to update it.`);
|
|
128
135
|
}
|
|
129
|
-
const relation = inspectGitUpstream(repoDir);
|
|
136
|
+
const relation = inspectGitUpstream(repoDir, credential);
|
|
130
137
|
if (relation.behind > 0 && relation.upstream) {
|
|
131
138
|
if (relation.ahead > 0) {
|
|
132
139
|
throw new UsageError(`Writable Git source at ${repoDir} has unpushed commits; refusing to update it.`);
|
|
@@ -120,30 +120,44 @@ export function saveGitStash(name, message, writableOverride, options) {
|
|
|
120
120
|
let managedContentRoot;
|
|
121
121
|
if (name) {
|
|
122
122
|
const config = loadConfig();
|
|
123
|
-
const stash =
|
|
123
|
+
const stash = findSyncStashByTarget(getSources(config), name);
|
|
124
124
|
// NotFoundError (exit 1), not UsageError (exit 2): the argument is
|
|
125
125
|
// well-formed, the bundle just isn't configured.
|
|
126
126
|
if (!stash)
|
|
127
|
-
throw new NotFoundError(`No git bundle found with name "${name}"`, "SOURCE_NOT_FOUND");
|
|
128
|
-
if (stash.
|
|
129
|
-
throw new UsageError(`
|
|
127
|
+
throw new NotFoundError(`No git-backed bundle found with name "${name}"`, "SOURCE_NOT_FOUND");
|
|
128
|
+
if (stash.enabled === false) {
|
|
129
|
+
throw new UsageError(`Bundle "${name}" is disabled and cannot be synced.`, "INVALID_FLAG_VALUE");
|
|
130
130
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
if (stash.type === "filesystem") {
|
|
132
|
+
if (!stash.path)
|
|
133
|
+
throw new UsageError(`Filesystem bundle "${name}" has no path configured.`);
|
|
134
|
+
const contentRoot = path.resolve(stash.path);
|
|
135
|
+
const topLevel = runGit(["-C", contentRoot, "rev-parse", "--show-toplevel"]);
|
|
134
136
|
if (topLevel.status !== 0 || !topLevel.stdout.trim()) {
|
|
135
|
-
throw new UsageError(`
|
|
137
|
+
throw new UsageError(`Filesystem bundle "${name}" is not a Git working tree at ${contentRoot}.`);
|
|
136
138
|
}
|
|
137
139
|
repoDir = path.resolve(topLevel.stdout.trim());
|
|
138
|
-
managedContentRoot =
|
|
140
|
+
managedContentRoot = contentRoot;
|
|
141
|
+
writable = stash.writable !== false;
|
|
139
142
|
}
|
|
140
143
|
else {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
144
|
+
const lockedRoot = lockContentRootFor(stash.name, stash.type);
|
|
145
|
+
if (lockedRoot) {
|
|
146
|
+
const topLevel = runGit(["-C", lockedRoot, "rev-parse", "--show-toplevel"]);
|
|
147
|
+
if (topLevel.status !== 0 || !topLevel.stdout.trim()) {
|
|
148
|
+
throw new UsageError(`Managed Git stash "${name}" is not a checkout at ${lockedRoot}`);
|
|
149
|
+
}
|
|
150
|
+
repoDir = path.resolve(topLevel.stdout.trim());
|
|
151
|
+
managedContentRoot = path.resolve(lockedRoot);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
if (!stash.url)
|
|
155
|
+
throw new UsageError(`Stash "${name}" has no URL configured`);
|
|
156
|
+
const repo = parseGitRepoUrl(stash.url);
|
|
157
|
+
repoDir = getCachePaths(repo.canonicalUrl).repoDir;
|
|
158
|
+
}
|
|
159
|
+
writable = stash.writable === true;
|
|
145
160
|
}
|
|
146
|
-
writable = stash.writable === true;
|
|
147
161
|
}
|
|
148
162
|
else {
|
|
149
163
|
// Honour an explicit primary-stash dir override (keeps the improve gate and
|
|
@@ -554,8 +568,16 @@ function createExactPathCommit(repoDir, options) {
|
|
|
554
568
|
fs.rmSync(`${temporaryIndex}.lock`, { force: true });
|
|
555
569
|
}
|
|
556
570
|
}
|
|
557
|
-
function
|
|
558
|
-
return stashes.find((stash) =>
|
|
571
|
+
function findSyncStashByTarget(stashes, target) {
|
|
572
|
+
return stashes.find((stash) => {
|
|
573
|
+
if (stash.type === "git")
|
|
574
|
+
return matchesGitStashTarget(stash, target);
|
|
575
|
+
if (stash.type !== "filesystem")
|
|
576
|
+
return false;
|
|
577
|
+
if (stash.name === target || stash.path === target)
|
|
578
|
+
return true;
|
|
579
|
+
return stash.path !== undefined && path.resolve(stash.path) === path.resolve(target);
|
|
580
|
+
});
|
|
559
581
|
}
|
|
560
582
|
function matchesGitStashTarget(stash, target) {
|
|
561
583
|
if (stash.type !== "git")
|
|
@@ -324,7 +324,7 @@ async function scrapeWebsiteToStash(startUrl, stashDir, options) {
|
|
|
324
324
|
export async function fetchWebsiteMarkdownSnapshot(rawUrl, options) {
|
|
325
325
|
const normalizedUrl = validateWebsiteInputUrl(rawUrl, { allowPrivateHosts: options?.allowPrivateHosts });
|
|
326
326
|
const parsedUrl = new URL(normalizedUrl);
|
|
327
|
-
const allowPrivateHosts = await resolveAllowPrivateStartHost(parsedUrl, options?.allowPrivateHosts);
|
|
327
|
+
const allowPrivateHosts = await resolveAllowPrivateStartHost(parsedUrl, options?.allowPrivateHosts, options?.resolveHostname);
|
|
328
328
|
const stashDir = resolveFetcherStashDir(options?.stashDir);
|
|
329
329
|
const context = {
|
|
330
330
|
stashDir: stashDir ?? "",
|
|
@@ -524,11 +524,11 @@ async function assertStartUrlAllowedByRobots(robots, start, rawStartUrl) {
|
|
|
524
524
|
* before it ever reaches a guard, so this never extends trust to a host the
|
|
525
525
|
* fetched content merely points at.
|
|
526
526
|
*/
|
|
527
|
-
async function resolveAllowPrivateStartHost(start, requested) {
|
|
527
|
+
async function resolveAllowPrivateStartHost(start, requested, resolveHostname) {
|
|
528
528
|
if (requested)
|
|
529
529
|
return true;
|
|
530
530
|
try {
|
|
531
|
-
await assertResolvedHostAllowed(start.hostname);
|
|
531
|
+
await assertResolvedHostAllowed(start.hostname, { resolveHostname });
|
|
532
532
|
return false;
|
|
533
533
|
}
|
|
534
534
|
catch {
|
|
@@ -106,6 +106,106 @@ export function isVecFastPathComplete(db) {
|
|
|
106
106
|
return false;
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Reconcile sqlite-vec's derived mirror from the durable BLOB embeddings.
|
|
111
|
+
*
|
|
112
|
+
* This never calls an embedding provider and never mutates the BLOB table.
|
|
113
|
+
* The readiness flag is lowered before the first mutation and is promoted only
|
|
114
|
+
* after a bidirectional aggregate check proves both ID sets match exactly.
|
|
115
|
+
*/
|
|
116
|
+
export function repairVecFastPath(db, embeddingDim) {
|
|
117
|
+
let repaired = 0;
|
|
118
|
+
let removedOrphans = 0;
|
|
119
|
+
let rejected = 0;
|
|
120
|
+
setVecFastPathReady(db, false);
|
|
121
|
+
if (!isVecAvailable(db) || !hasVecTable(db)) {
|
|
122
|
+
return { available: false, repaired, removedOrphans, rejected, complete: false };
|
|
123
|
+
}
|
|
124
|
+
if (!Number.isInteger(embeddingDim) || embeddingDim <= 0) {
|
|
125
|
+
return {
|
|
126
|
+
available: true,
|
|
127
|
+
repaired,
|
|
128
|
+
removedOrphans,
|
|
129
|
+
rejected,
|
|
130
|
+
complete: false,
|
|
131
|
+
error: `Invalid embedding dimension ${embeddingDim}.`,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
while (true) {
|
|
136
|
+
const orphanIds = db
|
|
137
|
+
.prepare(`
|
|
138
|
+
SELECT id FROM entries_vec
|
|
139
|
+
EXCEPT
|
|
140
|
+
SELECT id FROM embeddings
|
|
141
|
+
ORDER BY id
|
|
142
|
+
LIMIT ?
|
|
143
|
+
`)
|
|
144
|
+
.all(SQLITE_CHUNK_SIZE);
|
|
145
|
+
if (orphanIds.length === 0)
|
|
146
|
+
break;
|
|
147
|
+
db.transaction(() => {
|
|
148
|
+
const remove = db.prepare("DELETE FROM entries_vec WHERE id = ?");
|
|
149
|
+
for (const { id } of orphanIds) {
|
|
150
|
+
remove.run(id);
|
|
151
|
+
removedOrphans++;
|
|
152
|
+
}
|
|
153
|
+
})();
|
|
154
|
+
}
|
|
155
|
+
let afterId = -1;
|
|
156
|
+
while (true) {
|
|
157
|
+
const missingIds = db
|
|
158
|
+
.prepare(`
|
|
159
|
+
SELECT id FROM (
|
|
160
|
+
SELECT id FROM embeddings
|
|
161
|
+
EXCEPT
|
|
162
|
+
SELECT id FROM entries_vec
|
|
163
|
+
) AS missing
|
|
164
|
+
WHERE id > ?
|
|
165
|
+
ORDER BY id
|
|
166
|
+
LIMIT ?
|
|
167
|
+
`)
|
|
168
|
+
.all(afterId, SQLITE_CHUNK_SIZE);
|
|
169
|
+
if (missingIds.length === 0)
|
|
170
|
+
break;
|
|
171
|
+
afterId = missingIds[missingIds.length - 1].id;
|
|
172
|
+
const placeholders = missingIds.map(() => "?").join(",");
|
|
173
|
+
const rows = db
|
|
174
|
+
.prepare(`SELECT id, embedding FROM embeddings WHERE id IN (${placeholders}) ORDER BY id`)
|
|
175
|
+
.all(...missingIds.map(({ id }) => id));
|
|
176
|
+
db.transaction(() => {
|
|
177
|
+
const insert = db.prepare("INSERT INTO entries_vec (id, embedding) VALUES (?, ?)");
|
|
178
|
+
for (const row of rows) {
|
|
179
|
+
if (row.embedding.byteLength !== embeddingDim * 4) {
|
|
180
|
+
rejected++;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
insert.run(row.id, Buffer.from(row.embedding));
|
|
185
|
+
repaired++;
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
rejected++;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
})();
|
|
192
|
+
}
|
|
193
|
+
const complete = rejected === 0 && isVecFastPathComplete(db);
|
|
194
|
+
setVecFastPathReady(db, complete);
|
|
195
|
+
return { available: true, repaired, removedOrphans, rejected, complete };
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
setVecFastPathReady(db, false);
|
|
199
|
+
return {
|
|
200
|
+
available: true,
|
|
201
|
+
repaired,
|
|
202
|
+
removedOrphans,
|
|
203
|
+
rejected,
|
|
204
|
+
complete: false,
|
|
205
|
+
error: error instanceof Error ? error.message : String(error),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
109
209
|
const vecTablePresent = new WeakMap();
|
|
110
210
|
/**
|
|
111
211
|
* Whether `entries_vec` exists on this connection, memoized per handle.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import { bundleRefToString, parseBundleRef } from "../core/asset/asset-ref.js";
|
|
5
|
+
import { deriveBundleId } from "../core/bundle-id.js";
|
|
6
|
+
import { resolveStashDir } from "../core/common.js";
|
|
7
|
+
import { mutateConfig } from "../core/config/config.js";
|
|
8
|
+
import { bundleSourceId, filesystemBundleSourceId, isBundleEnabled } from "../core/config/config-sources.js";
|
|
9
|
+
import { UsageError } from "../core/errors.js";
|
|
10
|
+
export function canonicalSchedulerActivationRef(ref) {
|
|
11
|
+
const parsed = parseBundleRef(ref);
|
|
12
|
+
if (!parsed.bundle || parsed.fragment !== undefined || bundleRefToString(parsed) !== ref) {
|
|
13
|
+
throw new UsageError(`Scheduler activation requires a canonical fully-qualified ref, got ${JSON.stringify(ref)}.`, "INVALID_FLAG_VALUE");
|
|
14
|
+
}
|
|
15
|
+
return ref;
|
|
16
|
+
}
|
|
17
|
+
export function schedulerActivations(config) {
|
|
18
|
+
return Object.freeze((config.scheduler?.enabled ?? []).map((activation) => Object.freeze({
|
|
19
|
+
kind: activation.kind,
|
|
20
|
+
ref: canonicalSchedulerActivationRef(activation.ref),
|
|
21
|
+
sourceId: activation.sourceId,
|
|
22
|
+
})));
|
|
23
|
+
}
|
|
24
|
+
export function isSchedulerRefEnabled(config, kind, ref) {
|
|
25
|
+
const canonicalRef = canonicalSchedulerActivationRef(ref);
|
|
26
|
+
const bundle = parseBundleRef(canonicalRef).bundle;
|
|
27
|
+
const currentSourceId = schedulerActivationSourceId(config, bundle);
|
|
28
|
+
if (!currentSourceId)
|
|
29
|
+
return false;
|
|
30
|
+
return schedulerActivations(config).some((activation) => activation.kind === kind && activation.ref === canonicalRef && activation.sourceId === currentSourceId);
|
|
31
|
+
}
|
|
32
|
+
/** Grants that still name the same active source installation they approved. */
|
|
33
|
+
export function activeSchedulerActivations(config) {
|
|
34
|
+
return schedulerActivations(config).filter((activation) => {
|
|
35
|
+
const bundle = parseBundleRef(activation.ref).bundle;
|
|
36
|
+
return activation.sourceId === schedulerActivationSourceId(config, bundle);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/** Pure lifecycle helper used when a bundle is removed or replaced. */
|
|
40
|
+
export function revokeSchedulerActivationsForBundle(config, bundleId) {
|
|
41
|
+
const enabled = schedulerActivations(config).filter((activation) => parseBundleRef(activation.ref).bundle !== bundleId);
|
|
42
|
+
if (enabled.length === (config.scheduler?.enabled ?? []).length)
|
|
43
|
+
return config;
|
|
44
|
+
return { ...config, scheduler: { ...config.scheduler, enabled } };
|
|
45
|
+
}
|
|
46
|
+
export function setSchedulerRefEnabled(kind, ref, enabled) {
|
|
47
|
+
let canonicalRef = ref;
|
|
48
|
+
const result = mutateConfig((current) => {
|
|
49
|
+
canonicalRef = canonicalSchedulerActivationRef(ref);
|
|
50
|
+
const existing = schedulerActivations(current);
|
|
51
|
+
const bundle = parseBundleRef(canonicalRef).bundle;
|
|
52
|
+
const sourceId = enabled ? schedulerActivationSourceId(current, bundle) : undefined;
|
|
53
|
+
if (enabled && !sourceId) {
|
|
54
|
+
throw new UsageError(`Cannot enable scheduled execution from inactive or unconfigured bundle ${JSON.stringify(bundle)}.`, "INVALID_FLAG_VALUE");
|
|
55
|
+
}
|
|
56
|
+
const present = existing.some((activation) => activation.kind === kind && activation.ref === canonicalRef && (!enabled || activation.sourceId === sourceId));
|
|
57
|
+
if (present === enabled)
|
|
58
|
+
return current;
|
|
59
|
+
const next = enabled
|
|
60
|
+
? [
|
|
61
|
+
...existing.filter((activation) => activation.kind !== kind || activation.ref !== canonicalRef),
|
|
62
|
+
{ kind, ref: canonicalRef, sourceId: sourceId },
|
|
63
|
+
]
|
|
64
|
+
: existing.filter((activation) => activation.kind !== kind || activation.ref !== canonicalRef);
|
|
65
|
+
next.sort((left, right) => left.ref.localeCompare(right.ref) || left.kind.localeCompare(right.kind));
|
|
66
|
+
return {
|
|
67
|
+
...current,
|
|
68
|
+
scheduler: {
|
|
69
|
+
...current.scheduler,
|
|
70
|
+
enabled: next,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
return { config: result.config, changed: result.written, ref: canonicalRef };
|
|
75
|
+
}
|
|
76
|
+
/** Current active source identity for a configured or environment-only bundle. */
|
|
77
|
+
export function schedulerActivationSourceId(config, bundleId) {
|
|
78
|
+
if (isBundleEnabled(config, bundleId))
|
|
79
|
+
return bundleSourceId(config, bundleId);
|
|
80
|
+
if (config.bundles?.[bundleId] !== undefined || !process.env.AKM_BUNDLE_DIR?.trim())
|
|
81
|
+
return undefined;
|
|
82
|
+
try {
|
|
83
|
+
const root = resolveStashDir();
|
|
84
|
+
const implicitId = deriveBundleId(undefined, root, new Set(Object.keys(config.bundles ?? {})));
|
|
85
|
+
return implicitId === bundleId ? filesystemBundleSourceId(root) : undefined;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -41,6 +41,7 @@ import { nodeFs, throwIfNotOk } from "./exec-utils.js";
|
|
|
41
41
|
const BEGIN = (id) => `# akm:task ${assertCronValue(id)} BEGIN`;
|
|
42
42
|
const END = (id) => `# akm:task ${assertCronValue(id)} END`;
|
|
43
43
|
const DISABLED_PREFIX = "# akm:disabled ";
|
|
44
|
+
const SOURCE_ENABLED_PREFIX = "# akm:source-enabled ";
|
|
44
45
|
const BLOCK_RE = /^# akm:task ([\w.@:_-]+) BEGIN$/;
|
|
45
46
|
const BLOCK_END_RE = /^# akm:task ([\w.@:_-]+) END$/;
|
|
46
47
|
export const PORTABLE_CRON_LINE_LIMIT = 1000;
|
|
@@ -216,6 +217,7 @@ function inspectCronState(crontab, fallbackContextPath) {
|
|
|
216
217
|
continue;
|
|
217
218
|
const ref = {
|
|
218
219
|
id: schedulerLogicalBindingId(id, parsed.invocation),
|
|
220
|
+
enabled: !cronIsDisabled(body),
|
|
219
221
|
signature: fingerprint,
|
|
220
222
|
...(parsed.target !== undefined ? { target: parsed.target } : {}),
|
|
221
223
|
binding: parsed.binding,
|
|
@@ -438,9 +440,16 @@ function normalizeSignature(body) {
|
|
|
438
440
|
return body
|
|
439
441
|
.split(/\r?\n/)
|
|
440
442
|
.map((l) => l.trim())
|
|
443
|
+
.filter((l) => !stripDisabledPrefix(l).startsWith(SOURCE_ENABLED_PREFIX))
|
|
441
444
|
.filter((l) => l.length > 0)
|
|
442
445
|
.join("\n");
|
|
443
446
|
}
|
|
447
|
+
function stripDisabledPrefix(line) {
|
|
448
|
+
return line.startsWith(DISABLED_PREFIX) ? line.slice(DISABLED_PREFIX.length) : line;
|
|
449
|
+
}
|
|
450
|
+
function cronIsDisabled(body) {
|
|
451
|
+
return body.trimStart().startsWith(DISABLED_PREFIX);
|
|
452
|
+
}
|
|
444
453
|
export function upsertBlock(existing, id, block) {
|
|
445
454
|
const trimmed = existing.replace(/\s+$/g, "");
|
|
446
455
|
const removed = removeBlock(trimmed, id);
|
|
@@ -557,6 +557,7 @@ function inspectStableLaunchdNamespace(seedIds, context) {
|
|
|
557
557
|
continue;
|
|
558
558
|
installed.push(withInstalledInvocation({
|
|
559
559
|
id: schedulerLogicalBindingId(entry.nativeId, parsed.invocation),
|
|
560
|
+
enabled: entry.enabled && entry.loaded,
|
|
560
561
|
...(entry.loaded ? { signature: entry.artifact.fingerprint } : {}),
|
|
561
562
|
...(parsed.target !== undefined ? { target: parsed.target } : {}),
|
|
562
563
|
binding: parsed.binding,
|
|
@@ -257,8 +257,10 @@ function inspectSchtasksState(exec, folder, taskName) {
|
|
|
257
257
|
const parsed = extractSchtasksInvocation(query.stdout);
|
|
258
258
|
if (!parsed)
|
|
259
259
|
continue;
|
|
260
|
+
const enabled = taskXmlEnabled(query.stdout);
|
|
260
261
|
const ref = {
|
|
261
262
|
id: schedulerLogicalBindingId(nativeId, parsed.invocation),
|
|
263
|
+
...(enabled !== undefined ? { enabled } : {}),
|
|
262
264
|
...(artifact.fingerprint !== undefined ? { signature: artifact.fingerprint } : {}),
|
|
263
265
|
...(parsed.target !== undefined ? { target: parsed.target } : {}),
|
|
264
266
|
binding: parsed.binding,
|