akm-cli 0.7.4 → 0.8.0-rc.10
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 +224 -1
- package/README.md +22 -6
- package/SECURITY.md +93 -0
- package/dist/cli/config-migrate.js +144 -0
- package/dist/cli/config-validate.js +39 -0
- package/dist/cli/confirm.js +73 -0
- package/dist/cli/parse-args.js +133 -0
- package/dist/cli/shared.js +129 -0
- package/dist/cli.js +2631 -1440
- package/dist/commands/add-cli.js +279 -0
- package/dist/commands/agent-dispatch.js +110 -0
- package/dist/commands/agent-support.js +68 -0
- package/dist/commands/completions.js +3 -0
- package/dist/commands/config-cli.js +130 -534
- package/dist/commands/consolidate.js +2122 -0
- package/dist/commands/curate.js +45 -3
- package/dist/commands/db-cli.js +23 -0
- package/dist/commands/distill-promotion-policy.js +660 -0
- package/dist/commands/distill.js +1081 -73
- package/dist/commands/env.js +213 -0
- package/dist/commands/eval-cases.js +43 -0
- package/dist/commands/events.js +15 -24
- package/dist/commands/extract-cli.js +127 -0
- package/dist/commands/extract-prompt.js +204 -0
- package/dist/commands/extract.js +477 -0
- package/dist/commands/feedback-cli.js +331 -0
- package/dist/commands/graph.js +477 -0
- package/dist/commands/health.js +1302 -0
- package/dist/commands/help/help-accept.md +12 -0
- package/dist/commands/help/help-improve.md +69 -0
- package/dist/commands/help/help-proposals.md +18 -0
- package/dist/commands/help/help-propose.md +17 -0
- package/dist/commands/help/help-reject.md +11 -0
- package/dist/commands/history.js +54 -46
- package/dist/commands/improve-auto-accept.js +97 -0
- package/dist/commands/improve-cli.js +217 -0
- package/dist/commands/improve-profiles.js +166 -0
- package/dist/commands/improve-result-file.js +167 -0
- package/dist/commands/improve.js +2373 -0
- package/dist/commands/info.js +5 -2
- package/dist/commands/init.js +50 -2
- package/dist/commands/installed-stashes.js +102 -139
- package/dist/commands/knowledge.js +136 -0
- package/dist/commands/lint/agent-linter.js +49 -0
- package/dist/commands/lint/base-linter.js +479 -0
- package/dist/commands/lint/command-linter.js +49 -0
- package/dist/commands/lint/default-linter.js +16 -0
- package/dist/commands/lint/env-key-rules.js +154 -0
- package/dist/commands/lint/index.js +196 -0
- package/dist/commands/lint/knowledge-linter.js +16 -0
- package/dist/commands/lint/markdown-insertion.js +343 -0
- package/dist/commands/lint/memory-linter.js +61 -0
- package/dist/commands/lint/registry.js +36 -0
- package/dist/commands/lint/skill-linter.js +45 -0
- package/dist/commands/lint/task-linter.js +50 -0
- package/dist/commands/lint/types.js +4 -0
- package/dist/commands/lint/workflow-linter.js +56 -0
- package/dist/commands/lint.js +4 -0
- package/dist/commands/migration-help.js +3 -0
- package/dist/commands/proposal.js +67 -12
- package/dist/commands/propose.js +120 -45
- package/dist/commands/reflect.js +1104 -60
- package/dist/commands/registry-cli.js +150 -0
- package/dist/commands/registry-search.js +5 -2
- package/dist/commands/remember-cli.js +257 -0
- package/dist/commands/remember.js +70 -7
- package/dist/commands/schema-repair.js +203 -0
- package/dist/commands/search.js +115 -14
- package/dist/commands/secret.js +173 -0
- package/dist/commands/self-update.js +3 -0
- package/dist/commands/show.js +158 -60
- package/dist/commands/source-add.js +17 -45
- package/dist/commands/source-clone.js +3 -0
- package/dist/commands/source-manage.js +14 -19
- package/dist/commands/tasks.js +437 -0
- package/dist/commands/url-checker.js +42 -0
- package/dist/core/action-contributors.js +28 -0
- package/dist/core/asset-ref.js +17 -2
- package/dist/core/asset-registry.js +12 -17
- package/dist/core/asset-serialize.js +88 -0
- package/dist/core/asset-spec.js +67 -1
- package/dist/core/common.js +182 -0
- package/dist/core/concurrent.js +25 -0
- package/dist/core/config-io.js +347 -0
- package/dist/core/config-migration.js +622 -0
- package/dist/core/config-schema.js +534 -0
- package/dist/core/config-sources.js +108 -0
- package/dist/core/config-types.js +4 -0
- package/dist/core/config-walker.js +337 -0
- package/dist/core/config.js +364 -968
- package/dist/core/errors.js +42 -20
- package/dist/core/events.js +105 -135
- package/dist/core/file-lock.js +104 -0
- package/dist/core/frontmatter.js +75 -8
- package/dist/core/lesson-lint.js +3 -0
- package/dist/core/markdown.js +20 -0
- package/dist/core/memory-belief.js +62 -0
- package/dist/core/memory-contradiction-detect.js +274 -0
- package/dist/core/memory-improve.js +806 -0
- package/dist/core/parse.js +158 -0
- package/dist/core/paths.js +280 -14
- package/dist/core/proposal-quality-validators.js +380 -0
- package/dist/core/proposal-validators.js +69 -0
- package/dist/core/proposals.js +512 -42
- package/dist/core/state-db.js +1068 -0
- package/dist/core/text-truncation.js +107 -0
- package/dist/core/time.js +54 -0
- package/dist/core/tty.js +59 -0
- package/dist/core/warn.js +64 -1
- package/dist/core/write-source.js +3 -0
- package/dist/indexer/db-backup.js +391 -0
- package/dist/indexer/db-search.js +198 -489
- package/dist/indexer/db.js +990 -108
- package/dist/indexer/ensure-index.js +136 -0
- package/dist/indexer/file-context.js +3 -0
- package/dist/indexer/graph-boost.js +376 -101
- package/dist/indexer/graph-db.js +391 -0
- package/dist/indexer/graph-dedup.js +95 -0
- package/dist/indexer/graph-extraction.js +550 -114
- package/dist/indexer/index-context.js +4 -0
- package/dist/indexer/indexer.js +547 -309
- package/dist/indexer/llm-cache.js +52 -0
- package/dist/indexer/manifest.js +3 -0
- package/dist/indexer/matchers.js +167 -160
- package/dist/indexer/memory-inference.js +152 -74
- package/dist/indexer/metadata-contributors.js +29 -0
- package/dist/indexer/metadata.js +275 -196
- package/dist/indexer/path-resolver.js +92 -0
- package/dist/indexer/project-context.js +192 -0
- package/dist/indexer/ranking-contributors.js +331 -0
- package/dist/indexer/ranking.js +81 -0
- package/dist/indexer/search-fields.js +5 -9
- package/dist/indexer/search-hit-enrichers.js +111 -0
- package/dist/indexer/search-source.js +44 -10
- package/dist/indexer/semantic-status.js +6 -17
- package/dist/indexer/staleness-detect.js +447 -0
- package/dist/indexer/usage-events.js +12 -9
- package/dist/indexer/walker.js +28 -0
- package/dist/integrations/agent/builders.js +135 -0
- package/dist/integrations/agent/config.js +122 -230
- package/dist/integrations/agent/detect.js +3 -0
- package/dist/integrations/agent/index.js +7 -13
- package/dist/integrations/agent/model-aliases.js +55 -0
- package/dist/integrations/agent/profiles.js +70 -5
- package/dist/integrations/agent/prompts.js +250 -36
- package/dist/integrations/agent/runner.js +151 -0
- package/dist/integrations/agent/sdk-runner.js +126 -0
- package/dist/integrations/agent/spawn.js +183 -35
- package/dist/integrations/github.js +3 -0
- package/dist/integrations/lockfile.js +32 -69
- package/dist/integrations/session-logs/index.js +69 -0
- package/dist/integrations/session-logs/inline-refs.js +35 -0
- package/dist/integrations/session-logs/pre-filter.js +152 -0
- package/dist/integrations/session-logs/providers/claude-code.js +282 -0
- package/dist/integrations/session-logs/providers/opencode.js +258 -0
- package/dist/integrations/session-logs/types.js +4 -0
- package/dist/llm/call-ai.js +62 -0
- package/dist/llm/client.js +79 -88
- package/dist/llm/embedder.js +20 -29
- package/dist/llm/embedders/cache.js +3 -7
- package/dist/llm/embedders/local.js +42 -1
- package/dist/llm/embedders/remote.js +20 -8
- package/dist/llm/embedders/types.js +3 -7
- package/dist/llm/feature-gate.js +95 -48
- package/dist/llm/graph-extract.js +676 -72
- package/dist/llm/index-passes.js +44 -29
- package/dist/llm/memory-infer.js +80 -71
- package/dist/llm/metadata-enhance.js +42 -29
- package/dist/llm/prompts/extract-session.md +80 -0
- package/dist/llm/prompts/graph-extract-user-prompt.md +35 -0
- package/dist/output/cli-hints-full.md +292 -0
- package/dist/output/cli-hints-short.md +66 -0
- package/dist/output/cli-hints.js +7 -311
- package/dist/output/context.js +60 -8
- package/dist/output/renderers.js +306 -258
- package/dist/output/shapes/curate.js +56 -0
- package/dist/output/shapes/distill.js +10 -0
- package/dist/output/shapes/env-list.js +19 -0
- package/dist/output/shapes/events.js +11 -0
- package/dist/output/shapes/helpers.js +424 -0
- package/dist/output/shapes/history.js +7 -0
- package/dist/output/shapes/passthrough.js +102 -0
- package/dist/output/shapes/proposal-accept.js +7 -0
- package/dist/output/shapes/proposal-diff.js +7 -0
- package/dist/output/shapes/proposal-list.js +7 -0
- package/dist/output/shapes/proposal-producer.js +11 -0
- package/dist/output/shapes/proposal-reject.js +7 -0
- package/dist/output/shapes/proposal-show.js +7 -0
- package/dist/output/shapes/registry-search.js +6 -0
- package/dist/output/shapes/registry.js +30 -0
- package/dist/output/shapes/search.js +6 -0
- package/dist/output/shapes/secret-list.js +19 -0
- package/dist/output/shapes/show.js +6 -0
- package/dist/output/shapes/vault-list.js +19 -0
- package/dist/output/shapes.js +51 -511
- package/dist/output/text/add.js +6 -0
- package/dist/output/text/clone.js +6 -0
- package/dist/output/text/config.js +6 -0
- package/dist/output/text/curate.js +6 -0
- package/dist/output/text/distill.js +7 -0
- package/dist/output/text/enable-disable.js +7 -0
- package/dist/output/text/events.js +10 -0
- package/dist/output/text/feedback.js +6 -0
- package/dist/output/text/helpers.js +1039 -0
- package/dist/output/text/history.js +7 -0
- package/dist/output/text/import.js +6 -0
- package/dist/output/text/index.js +6 -0
- package/dist/output/text/info.js +6 -0
- package/dist/output/text/init.js +6 -0
- package/dist/output/text/list.js +6 -0
- package/dist/output/text/proposal-producer.js +8 -0
- package/dist/output/text/proposal.js +11 -0
- package/dist/output/text/registry-commands.js +11 -0
- package/dist/output/text/registry.js +30 -0
- package/dist/output/text/remember.js +6 -0
- package/dist/output/text/remove.js +6 -0
- package/dist/output/text/save.js +6 -0
- package/dist/output/text/search.js +6 -0
- package/dist/output/text/show.js +6 -0
- package/dist/output/text/update.js +6 -0
- package/dist/output/text/upgrade.js +6 -0
- package/dist/output/text/vault.js +16 -0
- package/dist/output/text/wiki.js +15 -0
- package/dist/output/text/workflow.js +14 -0
- package/dist/output/text.js +44 -1093
- package/dist/registry/build-index.js +3 -0
- package/dist/registry/create-provider-registry.js +3 -0
- package/dist/registry/factory.js +4 -1
- package/dist/registry/origin-resolve.js +3 -0
- package/dist/registry/providers/index.js +3 -0
- package/dist/registry/providers/skills-sh.js +71 -50
- package/dist/registry/providers/static-index.js +53 -48
- package/dist/registry/providers/types.js +3 -24
- package/dist/registry/resolve.js +11 -16
- package/dist/registry/types.js +3 -0
- package/dist/scripts/migrate-storage.js +17750 -0
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9031 -0
- package/dist/scripts/migrations/v16-to-v17.js +141 -0
- package/dist/setup/detect.js +3 -0
- package/dist/setup/ripgrep-install.js +3 -0
- package/dist/setup/ripgrep-resolve.js +3 -0
- package/dist/setup/setup.js +775 -37
- package/dist/setup/steps.js +3 -15
- package/dist/sources/include.js +3 -0
- package/dist/sources/provider-factory.js +5 -12
- package/dist/sources/provider.js +3 -20
- package/dist/sources/providers/filesystem.js +19 -23
- package/dist/sources/providers/git.js +179 -20
- package/dist/sources/providers/index.js +3 -0
- package/dist/sources/providers/install-types.js +3 -13
- package/dist/sources/providers/npm.js +3 -4
- package/dist/sources/providers/provider-utils.js +3 -0
- package/dist/sources/providers/sync-from-ref.js +3 -11
- package/dist/sources/providers/tar-utils.js +3 -0
- package/dist/sources/providers/website.js +18 -22
- package/dist/sources/resolve.js +3 -0
- package/dist/sources/types.js +3 -0
- package/dist/sources/website-ingest.js +7 -0
- package/dist/tasks/backends/cron.js +203 -0
- package/dist/tasks/backends/exec-utils.js +28 -0
- package/dist/tasks/backends/index.js +24 -0
- package/dist/tasks/backends/launchd-template.xml +19 -0
- package/dist/tasks/backends/launchd.js +187 -0
- package/dist/tasks/backends/schtasks-template.xml +29 -0
- package/dist/tasks/backends/schtasks.js +215 -0
- package/dist/tasks/parser.js +211 -0
- package/dist/tasks/resolveAkmBin.js +87 -0
- package/dist/tasks/runner.js +458 -0
- package/dist/tasks/schedule.js +227 -0
- package/dist/tasks/schema.js +15 -0
- package/dist/tasks/validator.js +62 -0
- package/dist/version.js +3 -0
- package/dist/wiki/index-template.md +12 -0
- package/dist/wiki/ingest-workflow-template.md +54 -0
- package/dist/wiki/log-template.md +8 -0
- package/dist/wiki/schema-template.md +61 -0
- package/dist/wiki/wiki-templates.js +15 -0
- package/dist/wiki/wiki.js +13 -61
- package/dist/workflows/authoring.js +8 -25
- package/dist/workflows/cli.js +3 -0
- package/dist/workflows/db.js +141 -2
- package/dist/workflows/document-cache.js +3 -10
- package/dist/workflows/parser.js +3 -0
- package/dist/workflows/renderer.js +11 -3
- package/dist/workflows/runs.js +91 -89
- package/dist/workflows/schema.js +3 -0
- package/dist/workflows/scope-key.js +79 -0
- package/dist/workflows/validator.js +4 -8
- package/dist/workflows/workflow-template.md +24 -0
- package/docs/README.md +10 -2
- package/docs/data-and-telemetry.md +225 -0
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.7.4.md +1 -1
- package/docs/migration/release-notes/0.7.5.md +20 -0
- package/docs/migration/release-notes/0.8.0.md +48 -0
- package/docs/migration/v0.7-to-v0.8.md +1307 -0
- package/package.json +29 -11
- package/dist/commands/install-audit.js +0 -381
- package/dist/commands/vault.js +0 -333
- package/dist/templates/wiki-templates.js +0 -100
package/dist/setup/steps.js
CHANGED
|
@@ -1,18 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* The interactive wizard in `setup.ts` historically ran a fixed series of
|
|
5
|
-
* step functions (`stepStashDir`, `stepOllama`, `stepLlm`, ...) inline.
|
|
6
|
-
* This module formalizes that pattern so steps can be:
|
|
7
|
-
* - reused by `akm init` (non-interactive preset, see Finding 31),
|
|
8
|
-
* - tested in isolation by passing a stub `SetupContext`, and
|
|
9
|
-
* - extended by plugins without touching the wizard call site.
|
|
10
|
-
*
|
|
11
|
-
* Steps mutate state through `SetupContext.apply()`, which accumulates a
|
|
12
|
-
* delta on top of the original config. `stepLlm` reading the embedding
|
|
13
|
-
* endpoint that `stepSemanticSearch` produced is the canonical example of
|
|
14
|
-
* why mutable accumulation is preferred over immutable returns.
|
|
15
|
-
*/
|
|
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/.
|
|
16
4
|
/**
|
|
17
5
|
* Build a fresh `SetupContext` over a starting config. The returned context
|
|
18
6
|
* applies deltas in-place onto an internal accumulator and exposes the
|
package/dist/sources/include.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
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/.
|
|
1
4
|
import fs from "node:fs";
|
|
2
5
|
import path from "node:path";
|
|
3
6
|
import { isWithin } from "../core/common";
|
|
@@ -1,14 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* to factory functions that build {@link SourceProvider} instances from a
|
|
6
|
-
* {@link SourceConfigEntry}.
|
|
7
|
-
*
|
|
8
|
-
* Distinct from the registry-discovery factory (`registry/factory.ts`).
|
|
9
|
-
* Both share `create-provider-registry.ts` for the underlying string→factory
|
|
10
|
-
* map.
|
|
11
|
-
*/
|
|
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 { getSources } from "../core/config";
|
|
12
5
|
import { createProviderRegistry } from "../registry/create-provider-registry";
|
|
13
6
|
// ── Factory map ─────────────────────────────────────────────────────────────
|
|
14
7
|
const registry = createProviderRegistry();
|
|
@@ -24,7 +17,7 @@ export function resolveSourceProviderFactory(type) {
|
|
|
24
17
|
*/
|
|
25
18
|
export function resolveSourceProviders(config) {
|
|
26
19
|
const providers = [];
|
|
27
|
-
for (const entry of config
|
|
20
|
+
for (const entry of getSources(config)) {
|
|
28
21
|
if (entry.enabled === false)
|
|
29
22
|
continue;
|
|
30
23
|
const factory = registry.resolve(entry.type);
|
package/dist/sources/provider.js
CHANGED
|
@@ -1,21 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* A SourceProvider gets files into a directory. The indexer walks `path()`
|
|
5
|
-
* and reads files from disk. Search and show go through the indexer, not
|
|
6
|
-
* through provider methods.
|
|
7
|
-
*
|
|
8
|
-
* Three required members + one optional:
|
|
9
|
-
* - name configured source name
|
|
10
|
-
* - kind "filesystem" | "git" | "website" | "npm"
|
|
11
|
-
* - init(ctx) called once after construction
|
|
12
|
-
* - path() the directory the indexer walks (stable for instance lifetime)
|
|
13
|
-
* - sync?() refresh the directory from upstream (no-op for filesystem)
|
|
14
|
-
*
|
|
15
|
-
* All other writing/reading concerns live outside this interface:
|
|
16
|
-
* - Writes: src/core/write-source.ts (Phase 5)
|
|
17
|
-
* - Reads: src/indexer.ts (Phase 4)
|
|
18
|
-
* - Install: src/sources/providers/sync-from-ref.ts (install-time helpers,
|
|
19
|
-
* separate from configured-source plumbing)
|
|
20
|
-
*/
|
|
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/.
|
|
21
4
|
export {};
|
|
@@ -1,3 +1,6 @@
|
|
|
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/.
|
|
1
4
|
import { resolveStashDir } from "../../core/common";
|
|
2
5
|
import { ConfigError } from "../../core/errors";
|
|
3
6
|
import { registerSourceProvider } from "../provider-factory";
|
|
@@ -8,28 +11,21 @@ import { registerSourceProvider } from "../provider-factory";
|
|
|
8
11
|
* just `{ name, kind, init, path }`. No `sync()` — content is the user's
|
|
9
12
|
* own directory, never refreshed by akm.
|
|
10
13
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
#stashDir;
|
|
15
|
-
constructor(entry) {
|
|
16
|
-
if (entry.type !== "filesystem") {
|
|
17
|
-
throw new ConfigError(`FilesystemSourceProvider invoked with type="${entry.type}"`);
|
|
18
|
-
}
|
|
19
|
-
this.#stashDir = entry.path ?? resolveStashDir();
|
|
20
|
-
if (!this.#stashDir) {
|
|
21
|
-
throw new ConfigError("filesystem source requires a `path`");
|
|
22
|
-
}
|
|
23
|
-
this.name = entry.name ?? this.#stashDir;
|
|
14
|
+
registerSourceProvider("filesystem", (entry) => {
|
|
15
|
+
if (entry.type !== "filesystem") {
|
|
16
|
+
throw new ConfigError(`filesystem source invoked with type="${entry.type}"`);
|
|
24
17
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
18
|
+
const stashDir = entry.path ?? resolveStashDir();
|
|
19
|
+
if (!stashDir) {
|
|
20
|
+
throw new ConfigError("filesystem source requires a `path`");
|
|
28
21
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
22
|
+
const name = entry.name ?? stashDir;
|
|
23
|
+
return {
|
|
24
|
+
kind: "filesystem",
|
|
25
|
+
name,
|
|
26
|
+
async init(_ctx) { },
|
|
27
|
+
path() {
|
|
28
|
+
return stashDir;
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
});
|
|
@@ -1,10 +1,13 @@
|
|
|
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/.
|
|
1
4
|
import { spawnSync } from "node:child_process";
|
|
2
5
|
import { createHash, randomBytes } from "node:crypto";
|
|
3
6
|
import fs from "node:fs";
|
|
4
7
|
import path from "node:path";
|
|
5
8
|
import { TYPE_DIRS } from "../../core/asset-spec";
|
|
6
9
|
import { resolveStashDir } from "../../core/common";
|
|
7
|
-
import { loadConfig } from "../../core/config";
|
|
10
|
+
import { getSources, loadConfig } from "../../core/config";
|
|
8
11
|
import { ConfigError, UsageError } from "../../core/errors";
|
|
9
12
|
import { getRegistryCacheDir, getRegistryIndexCacheDir } from "../../core/paths";
|
|
10
13
|
import { sanitizeCommitMessage } from "../../core/write-source";
|
|
@@ -15,7 +18,13 @@ import { applyAkmIncludeConfig, buildInstallCacheDir, copyDirectoryContents, det
|
|
|
15
18
|
const CACHE_TTL_MS = 12 * 60 * 60 * 1000;
|
|
16
19
|
/** Maximum stale age allowed when refresh fails (7 days). */
|
|
17
20
|
const CACHE_STALE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
18
|
-
|
|
21
|
+
function runGit(args, options) {
|
|
22
|
+
return spawnSync("git", args, {
|
|
23
|
+
encoding: "utf8",
|
|
24
|
+
...options,
|
|
25
|
+
env: { ...process.env, ...options?.env, GIT_TERMINAL_PROMPT: "0" },
|
|
26
|
+
});
|
|
27
|
+
}
|
|
19
28
|
/**
|
|
20
29
|
* Git source provider — clones (and re-pulls) a remote repo into a local
|
|
21
30
|
* cache directory. Implements the v1 {@link SourceProvider} interface (spec
|
|
@@ -219,10 +228,9 @@ async function doSyncGit(parsed, options) {
|
|
|
219
228
|
cloneArgs.push("--branch", parsed.requestedRef);
|
|
220
229
|
}
|
|
221
230
|
cloneArgs.push(parsed.url, cloneDir);
|
|
222
|
-
const cloneResult =
|
|
231
|
+
const cloneResult = runGit(cloneArgs, { timeout: 120_000 });
|
|
223
232
|
if (cloneResult.status !== 0) {
|
|
224
|
-
|
|
225
|
-
throw new Error(`Failed to clone ${parsed.url}: ${err}`);
|
|
233
|
+
throw new Error(classifyCloneFailure(parsed.url, cloneResult.stderr, cloneResult.error));
|
|
226
234
|
}
|
|
227
235
|
// Copy contents to extracted dir without .git
|
|
228
236
|
fs.mkdirSync(extractedDir, { recursive: true });
|
|
@@ -267,12 +275,11 @@ export function cloneRepo(cloneUrl, ref, destDir, writable = false) {
|
|
|
267
275
|
if (ref)
|
|
268
276
|
args.push("--branch", ref);
|
|
269
277
|
args.push(cloneUrl, tmpDir);
|
|
270
|
-
const result =
|
|
278
|
+
const result = runGit(args, { timeout: 120_000 });
|
|
271
279
|
if (result.status !== 0) {
|
|
272
280
|
// Clean up the (possibly partial) temp dir but leave destDir untouched.
|
|
273
281
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
274
|
-
|
|
275
|
-
throw new Error(`Failed to clone ${cloneUrl}: ${err}`);
|
|
282
|
+
throw new Error(classifyCloneFailure(cloneUrl, result.stderr, result.error));
|
|
276
283
|
}
|
|
277
284
|
try {
|
|
278
285
|
if (!writable) {
|
|
@@ -293,8 +300,7 @@ export function cloneRepo(cloneUrl, ref, destDir, writable = false) {
|
|
|
293
300
|
}
|
|
294
301
|
}
|
|
295
302
|
function pullRepo(repoDir) {
|
|
296
|
-
const result =
|
|
297
|
-
encoding: "utf8",
|
|
303
|
+
const result = runGit(["-C", repoDir, "pull", "--ff-only"], {
|
|
298
304
|
timeout: 120_000,
|
|
299
305
|
});
|
|
300
306
|
if (result.status !== 0) {
|
|
@@ -395,7 +401,10 @@ function parseGitRepoUrl(rawUrl) {
|
|
|
395
401
|
* When `name` is omitted the primary stash directory is used.
|
|
396
402
|
* When `message` is omitted a timestamp is used.
|
|
397
403
|
*/
|
|
398
|
-
export function saveGitStash(name, message, writableOverride) {
|
|
404
|
+
export function saveGitStash(name, message, writableOverride, options) {
|
|
405
|
+
// `push: false` (from `akm sync --no-push`) commits but never pushes, even
|
|
406
|
+
// when the stash is writable with a remote configured.
|
|
407
|
+
const allowPush = options?.push !== false;
|
|
399
408
|
const timestamp = new Date().toISOString().replace("T", " ").slice(0, 19);
|
|
400
409
|
// Sanitize the user-supplied message: strip CR/LF/NUL, collapse whitespace,
|
|
401
410
|
// clamp length. An attacker can otherwise pass `--message "subject\n\n\
|
|
@@ -407,10 +416,10 @@ export function saveGitStash(name, message, writableOverride) {
|
|
|
407
416
|
let writable = false;
|
|
408
417
|
if (name) {
|
|
409
418
|
const config = loadConfig();
|
|
410
|
-
const stash = (config
|
|
419
|
+
const stash = findGitStashByTarget(getSources(config), name);
|
|
411
420
|
if (!stash)
|
|
412
421
|
throw new UsageError(`No git stash found with name "${name}"`);
|
|
413
|
-
if (
|
|
422
|
+
if (stash.type !== "git") {
|
|
414
423
|
throw new UsageError(`Stash "${name}" is not a git stash (type: ${stash.type})`);
|
|
415
424
|
}
|
|
416
425
|
if (!stash.url)
|
|
@@ -431,33 +440,59 @@ export function saveGitStash(name, message, writableOverride) {
|
|
|
431
440
|
return { committed: false, pushed: false, skipped: true, reason: "not a git repository", output: "" };
|
|
432
441
|
}
|
|
433
442
|
// Nothing to commit?
|
|
434
|
-
const statusResult =
|
|
443
|
+
const statusResult = runGit(["-C", repoDir, "status", "--porcelain"]);
|
|
435
444
|
if (statusResult.error || statusResult.status !== 0) {
|
|
436
445
|
throw new Error(`git status failed: ${statusResult.error?.message || statusResult.stderr?.trim() || "unknown error"}`);
|
|
437
446
|
}
|
|
438
447
|
if (!statusResult.stdout.trim()) {
|
|
439
448
|
return { committed: false, pushed: false, skipped: false, output: "nothing to commit, working tree clean" };
|
|
440
449
|
}
|
|
450
|
+
// Safety check (#476): when the stash dir is shared with a non-akm project
|
|
451
|
+
// (stash root == project repo root), `git add -A` would stage every dirty
|
|
452
|
+
// file in the user's working tree and push their unrelated WIP to the
|
|
453
|
+
// stash's remote. Refuse if any dirty path is outside the known akm-
|
|
454
|
+
// managed subtrees (TYPE_DIRS + `.akm/` state).
|
|
455
|
+
const nonAkmDirty = collectNonAkmDirtyPaths(statusResult.stdout);
|
|
456
|
+
if (nonAkmDirty.length > 0) {
|
|
457
|
+
const sample = nonAkmDirty.slice(0, 10);
|
|
458
|
+
const more = nonAkmDirty.length > sample.length ? `\n ...and ${nonAkmDirty.length - sample.length} more` : "";
|
|
459
|
+
throw new Error(`refusing to push: stash repo at ${repoDir} has uncommitted non-akm changes:\n` +
|
|
460
|
+
sample.map((p) => ` ${p}`).join("\n") +
|
|
461
|
+
more +
|
|
462
|
+
`\nCommit or stash these manually before running an akm push. ` +
|
|
463
|
+
`Akm-managed paths are: ${Object.values(TYPE_DIRS).join(", ")}, .akm/`);
|
|
464
|
+
}
|
|
441
465
|
// Stage and commit — supply fallback identity so fresh environments without
|
|
442
466
|
// user.name/user.email configured can always commit to the default stash.
|
|
443
|
-
|
|
467
|
+
// `add -A` is safe here because nonAkmDirty was just verified empty.
|
|
468
|
+
const addResult = runGit(["-C", repoDir, "add", "-A"]);
|
|
444
469
|
if (addResult.status !== 0) {
|
|
445
470
|
throw new Error(`git add failed: ${addResult.stderr?.trim() || "unknown error"}`);
|
|
446
471
|
}
|
|
447
|
-
const commitResult =
|
|
472
|
+
const commitResult = runGit([
|
|
473
|
+
"-C",
|
|
474
|
+
repoDir,
|
|
475
|
+
"-c",
|
|
476
|
+
"user.name=akm",
|
|
477
|
+
"-c",
|
|
478
|
+
"user.email=akm@local",
|
|
479
|
+
"commit",
|
|
480
|
+
"-m",
|
|
481
|
+
commitMessage,
|
|
482
|
+
]);
|
|
448
483
|
if (commitResult.status !== 0) {
|
|
449
484
|
throw new Error(`git commit failed: ${commitResult.stderr?.trim() || "unknown error"}`);
|
|
450
485
|
}
|
|
451
486
|
// Push only when there is a remote AND the stash is marked writable
|
|
452
|
-
const remoteResult =
|
|
487
|
+
const remoteResult = runGit(["-C", repoDir, "remote"]);
|
|
453
488
|
if (remoteResult.status !== 0) {
|
|
454
489
|
throw new Error(`git remote failed: ${remoteResult.stderr?.trim() || "unknown error"}`);
|
|
455
490
|
}
|
|
456
491
|
const hasRemote = remoteResult.stdout.trim().length > 0;
|
|
457
|
-
if (!hasRemote || !writable) {
|
|
492
|
+
if (!hasRemote || !writable || !allowPush) {
|
|
458
493
|
return { committed: true, pushed: false, skipped: false, output: commitResult.stdout.trim() };
|
|
459
494
|
}
|
|
460
|
-
const pushResult =
|
|
495
|
+
const pushResult = runGit(["-C", repoDir, "push"], { timeout: 120_000 });
|
|
461
496
|
if (pushResult.status !== 0) {
|
|
462
497
|
throw new Error(`git push failed: ${pushResult.stderr?.trim() || "unknown error"}`);
|
|
463
498
|
}
|
|
@@ -468,5 +503,129 @@ export function saveGitStash(name, message, writableOverride) {
|
|
|
468
503
|
output: (commitResult.stdout + pushResult.stdout).trim() || "changes committed and pushed",
|
|
469
504
|
};
|
|
470
505
|
}
|
|
506
|
+
function findGitStashByTarget(stashes, target) {
|
|
507
|
+
return stashes.find((stash) => matchesGitStashTarget(stash, target));
|
|
508
|
+
}
|
|
509
|
+
function matchesGitStashTarget(stash, target) {
|
|
510
|
+
if (stash.type !== "git")
|
|
511
|
+
return false;
|
|
512
|
+
if (stash.name === target || stash.url === target)
|
|
513
|
+
return true;
|
|
514
|
+
if (!stash.url)
|
|
515
|
+
return false;
|
|
516
|
+
try {
|
|
517
|
+
const repo = parseGitRepoUrl(stash.url);
|
|
518
|
+
if (repo.canonicalUrl === target)
|
|
519
|
+
return true;
|
|
520
|
+
return buildGithubTargetAliases(repo.canonicalUrl).has(target);
|
|
521
|
+
}
|
|
522
|
+
catch {
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
function buildGithubTargetAliases(canonicalUrl) {
|
|
527
|
+
try {
|
|
528
|
+
const parsed = new URL(canonicalUrl);
|
|
529
|
+
if (parsed.hostname !== "github.com")
|
|
530
|
+
return new Set();
|
|
531
|
+
const segments = parsed.pathname.split("/").filter(Boolean);
|
|
532
|
+
if (segments.length < 2)
|
|
533
|
+
return new Set();
|
|
534
|
+
const owner = segments[0];
|
|
535
|
+
const repo = segments[1];
|
|
536
|
+
const aliases = new Set([`${owner}/${repo}`, `github:${owner}/${repo}`]);
|
|
537
|
+
if (segments[2] === "tree" && segments.length >= 4) {
|
|
538
|
+
const ref = segments.slice(3).join("/");
|
|
539
|
+
aliases.add(`${owner}/${repo}#${ref}`);
|
|
540
|
+
aliases.add(`github:${owner}/${repo}#${ref}`);
|
|
541
|
+
}
|
|
542
|
+
return aliases;
|
|
543
|
+
}
|
|
544
|
+
catch {
|
|
545
|
+
return new Set();
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
// ── Clone-failure classification (#487) ─────────────────────────────────────
|
|
549
|
+
/**
|
|
550
|
+
* Translate git's stderr into an actionable message. Without this, a user
|
|
551
|
+
* who passes a nonexistent or private repo to `akm add` sees:
|
|
552
|
+
*
|
|
553
|
+
* "could not read Username for 'https://github.com': No such device or
|
|
554
|
+
* address"
|
|
555
|
+
*
|
|
556
|
+
* That is git falling through to its auth-prompt path — the actual cause
|
|
557
|
+
* is "repo doesn't exist (or is private)". We classify the common patterns
|
|
558
|
+
* and emit a message that names the cause and the fix.
|
|
559
|
+
*/
|
|
560
|
+
export function classifyCloneFailure(url, stderr, spawnError) {
|
|
561
|
+
const raw = (stderr ?? "").trim();
|
|
562
|
+
const spawnMsg = spawnError?.message ?? "";
|
|
563
|
+
// `git` binary not on PATH.
|
|
564
|
+
if (spawnError?.code === "ENOENT") {
|
|
565
|
+
return `Failed to clone ${url}: 'git' is not installed or not on PATH. Install git, then re-run.`;
|
|
566
|
+
}
|
|
567
|
+
// Auth-prompt fall-through (the headline #487 case).
|
|
568
|
+
if (/could not read Username|terminal prompts disabled|Authentication failed|fatal: Authentication/i.test(raw)) {
|
|
569
|
+
return (`Failed to clone ${url}: repository not found or private. ` +
|
|
570
|
+
`If the repository is public, double-check the URL and try again. ` +
|
|
571
|
+
`If it is private, set GH_TOKEN (or configure a git credential helper) before re-running.`);
|
|
572
|
+
}
|
|
573
|
+
// 404-style messages from git http.
|
|
574
|
+
if (/repository '.*' not found|HTTP 404|fatal: remote error|not found:|Not Found/i.test(raw)) {
|
|
575
|
+
return (`Failed to clone ${url}: repository not found. ` +
|
|
576
|
+
`Check the URL — for GitHub, the form is 'owner/repo' or 'github:owner/repo'.`);
|
|
577
|
+
}
|
|
578
|
+
// SSH connection issues.
|
|
579
|
+
if (/Permission denied \(publickey\)|kex_exchange_identification|Connection refused|Connection timed out/i.test(raw)) {
|
|
580
|
+
return (`Failed to clone ${url}: network or SSH failure. ` +
|
|
581
|
+
`Check connectivity, your SSH agent, and the remote host's availability.`);
|
|
582
|
+
}
|
|
583
|
+
// Branch / ref-specific failures.
|
|
584
|
+
if (/Remote branch .* not found in upstream origin|couldn't find remote ref/i.test(raw)) {
|
|
585
|
+
return (`Failed to clone ${url}: the requested branch/tag does not exist on the remote. ` +
|
|
586
|
+
`Verify the ref name and re-run.`);
|
|
587
|
+
}
|
|
588
|
+
const detail = raw || spawnMsg || "unknown error";
|
|
589
|
+
return `Failed to clone ${url}: ${detail}`;
|
|
590
|
+
}
|
|
591
|
+
// ── Stash-safety helpers (#476) ──────────────────────────────────────────────
|
|
592
|
+
/**
|
|
593
|
+
* Inspect `git status --porcelain` output and return every dirty path that is
|
|
594
|
+
* NOT inside an akm-managed subtree. Used by `runUpstreamPush` to refuse
|
|
595
|
+
* pushing unrelated WIP when a writable stash shares its root with a project
|
|
596
|
+
* repo.
|
|
597
|
+
*
|
|
598
|
+
* Porcelain v1 format: `XY <path>` or `XY <orig> -> <new>` for renames. We
|
|
599
|
+
* key off the post-rename path (or the only path) — that is the working-tree
|
|
600
|
+
* file at risk of being staged by `git add -A`.
|
|
601
|
+
*/
|
|
602
|
+
function collectNonAkmDirtyPaths(porcelainOutput) {
|
|
603
|
+
const akmDirs = new Set(Object.values(TYPE_DIRS));
|
|
604
|
+
const result = [];
|
|
605
|
+
for (const rawLine of porcelainOutput.split("\n")) {
|
|
606
|
+
const line = rawLine.replace(/\r$/, "");
|
|
607
|
+
if (line.length === 0)
|
|
608
|
+
continue;
|
|
609
|
+
// Skip the 2-char status code + 1 space.
|
|
610
|
+
let p = line.length > 3 ? line.slice(3) : "";
|
|
611
|
+
// Renames / copies: `from -> to`. Stage decision applies to `to`.
|
|
612
|
+
const arrow = p.lastIndexOf(" -> ");
|
|
613
|
+
if (arrow !== -1) {
|
|
614
|
+
p = p.slice(arrow + 4);
|
|
615
|
+
}
|
|
616
|
+
// Strip surrounding quotes for paths with special chars.
|
|
617
|
+
if (p.startsWith('"') && p.endsWith('"') && p.length >= 2) {
|
|
618
|
+
p = p.slice(1, -1);
|
|
619
|
+
}
|
|
620
|
+
if (!p)
|
|
621
|
+
continue;
|
|
622
|
+
const segments = p.split("/");
|
|
623
|
+
const top = segments[0];
|
|
624
|
+
if (top === ".akm" || akmDirs.has(top))
|
|
625
|
+
continue;
|
|
626
|
+
result.push(p);
|
|
627
|
+
}
|
|
628
|
+
return result;
|
|
629
|
+
}
|
|
471
630
|
// ── Exports ─────────────────────────────────────────────────────────────────
|
|
472
|
-
export { ensureGitMirror, GitSourceProvider, getCachePaths, parseGitRepoUrl };
|
|
631
|
+
export { collectNonAkmDirtyPaths, ensureGitMirror, GitSourceProvider, getCachePaths, parseGitRepoUrl };
|
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Distinct from the v1 {@link SourceProvider} interface (which only deals
|
|
5
|
-
* with "configured sources" — entries already resolved into a directory).
|
|
6
|
-
* These types describe the resolution+lockfile step that runs when
|
|
7
|
-
* `akm add <install-ref>` materialises an upstream artifact into a local
|
|
8
|
-
* cache directory.
|
|
9
|
-
*
|
|
10
|
-
* They live here, outside `provider.ts`, so the v1 SourceProvider
|
|
11
|
-
* interface stays minimal (`{ name, kind, init, path, sync? }`) per the
|
|
12
|
-
* architecture spec §2.1.
|
|
13
|
-
*/
|
|
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/.
|
|
14
4
|
export {};
|
|
@@ -1,3 +1,6 @@
|
|
|
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/.
|
|
1
4
|
/**
|
|
2
5
|
* Npm-source stash provider.
|
|
3
6
|
*
|
|
@@ -5,10 +8,6 @@
|
|
|
5
8
|
* integrity, extracts it securely (via `extractTarGzSecure`), detects the
|
|
6
9
|
* stash root inside the package, and applies any nested `.akm-include`
|
|
7
10
|
* configuration. Cache hits short-circuit the fetch.
|
|
8
|
-
*
|
|
9
|
-
* Audit is intentionally NOT performed here — `akmAdd` calls
|
|
10
|
-
* `auditInstallCandidate` after `sync()` so the policy decision lives at
|
|
11
|
-
* the orchestrator layer where the `--trust` flag is known.
|
|
12
11
|
*/
|
|
13
12
|
import fs from "node:fs";
|
|
14
13
|
import path from "node:path";
|
|
@@ -1,3 +1,6 @@
|
|
|
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/.
|
|
1
4
|
import { createHash } from "node:crypto";
|
|
2
5
|
import fs from "node:fs";
|
|
3
6
|
import path from "node:path";
|
|
@@ -1,14 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Replaces the historical `installRegistryRef()` entry point. Given an
|
|
5
|
-
* unparsed install ref, this resolves the right syncable provider and
|
|
6
|
-
* invokes its `sync()` method.
|
|
7
|
-
*
|
|
8
|
-
* Audit is intentionally NOT performed here; callers (`akmAdd`,
|
|
9
|
-
* `akmUpdate`) decide whether to run `auditInstallCandidate` on the
|
|
10
|
-
* synced `contentDir` because they own the `--trust` flag.
|
|
11
|
-
*/
|
|
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/.
|
|
12
4
|
import { UsageError } from "../../core/errors";
|
|
13
5
|
import { parseRegistryRef } from "../../registry/resolve";
|
|
14
6
|
import { detectStashRoot } from "./provider-utils";
|
|
@@ -1,3 +1,6 @@
|
|
|
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/.
|
|
1
4
|
/**
|
|
2
5
|
* Tar archive extraction and integrity verification utilities.
|
|
3
6
|
*
|
|
@@ -1,27 +1,23 @@
|
|
|
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/.
|
|
1
4
|
import { registerSourceProvider } from "../provider-factory";
|
|
2
5
|
import { ensureWebsiteMirror, getWebsiteCachePaths, validateWebsiteUrl } from "../website-ingest";
|
|
3
6
|
/**
|
|
4
7
|
* Website source provider — thin adapter over the shared website ingest module.
|
|
5
8
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
name;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
async sync() {
|
|
23
|
-
await ensureWebsiteMirror(this.#config, { requireStashDir: true });
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
registerSourceProvider("website", (config) => new WebsiteSourceProvider(config));
|
|
27
|
-
export { WebsiteSourceProvider };
|
|
9
|
+
registerSourceProvider("website", (config) => {
|
|
10
|
+
const url = validateWebsiteUrl(config.url ?? "");
|
|
11
|
+
const name = config.name ?? "website";
|
|
12
|
+
return {
|
|
13
|
+
kind: "website",
|
|
14
|
+
name,
|
|
15
|
+
async init(_ctx) { },
|
|
16
|
+
path() {
|
|
17
|
+
return getWebsiteCachePaths(url).stashDir;
|
|
18
|
+
},
|
|
19
|
+
async sync() {
|
|
20
|
+
await ensureWebsiteMirror(config, { requireStashDir: true });
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
});
|
package/dist/sources/resolve.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
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/.
|
|
1
4
|
import fs from "node:fs";
|
|
2
5
|
import path from "node:path";
|
|
3
6
|
import { deriveCanonicalAssetNameFromStashRoot, isRelevantAssetFile, resolveAssetPathFromName, TYPE_DIRS, } from "../core/asset-spec";
|
package/dist/sources/types.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
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/.
|
|
1
4
|
import { createHash } from "node:crypto";
|
|
2
5
|
import fs from "node:fs";
|
|
3
6
|
import path from "node:path";
|
|
@@ -167,6 +170,10 @@ async function crawlWebsite(startUrl, options) {
|
|
|
167
170
|
return pages;
|
|
168
171
|
}
|
|
169
172
|
async function fetchWebsitePage(pageUrl) {
|
|
173
|
+
const parsedUrl = new URL(pageUrl);
|
|
174
|
+
if (parsedUrl.hostname.endsWith(".invalid")) {
|
|
175
|
+
throw new Error(`Refusing to fetch reserved invalid hostname: ${parsedUrl.hostname}`);
|
|
176
|
+
}
|
|
170
177
|
const response = await fetchWithRetry(pageUrl, {
|
|
171
178
|
headers: {
|
|
172
179
|
Accept: "text/html, text/markdown, text/plain;q=0.9, application/xhtml+xml;q=0.8",
|