@indigoai-us/hq-cli 5.77.14 → 5.79.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.
- package/CHANGELOG.md +31 -0
- package/assets/scaffold/core/scripts/archive-old-threads.sh +86 -0
- package/assets/scaffold/core/scripts/backfill-company-skill-mirrors.sh +73 -0
- package/assets/scaffold/core/scripts/backfill-workspace-mirror.sh +51 -0
- package/assets/scaffold/core/scripts/codex-skill-bridge.sh +550 -0
- package/assets/scaffold/core/scripts/detect-stale-core-policy-mirror.sh +185 -0
- package/assets/scaffold/core/scripts/hq-status-summary.sh +187 -0
- package/assets/scaffold/core/scripts/ontology-readme-drift.sh +264 -0
- package/assets/scaffold/core/scripts/qmd-reindex-after-sync.sh +93 -0
- package/assets/scaffold/core/scripts/rebuild-all-indexes.sh +69 -0
- package/assets/scaffold/core/scripts/rebuild-companies-index.sh +206 -0
- package/assets/scaffold/core/scripts/rebuild-company-knowledge-index.sh +127 -0
- package/assets/scaffold/core/scripts/rebuild-orchestrator-index.sh +86 -0
- package/assets/scaffold/core/scripts/rebuild-projects-index.sh +105 -0
- package/assets/scaffold/core/scripts/rebuild-public-knowledge-index.sh +83 -0
- package/assets/scaffold/core/scripts/rebuild-reports-index.sh +114 -0
- package/assets/scaffold/core/scripts/rebuild-social-drafts-index.sh +135 -0
- package/assets/scaffold/core/scripts/rebuild-threads-index.sh +82 -0
- package/assets/scaffold/core/scripts/rebuild-workers-index.sh +97 -0
- package/assets/scaffold/core/scripts/resize-screenshot.sh +47 -0
- package/assets/scaffold/core/scripts/token-usage-report.sh +254 -0
- package/assets/scaffold/core/scripts/worktree.sh +136 -0
- package/dist/commands/agents.js +28 -12
- package/dist/commands/api-keys.js +100 -20
- package/dist/commands/core.d.ts +90 -0
- package/dist/commands/core.js +279 -0
- package/dist/commands/files-browse.d.ts +21 -0
- package/dist/commands/files-browse.js +145 -24
- package/dist/commands/files.js +33 -7
- package/dist/commands/meetings.js +16 -9
- package/dist/commands/members.js +16 -5
- package/dist/commands/pack-install.d.ts +14 -0
- package/dist/commands/pack-install.js +42 -2
- package/dist/commands/pkg-install.js +39 -2
- package/dist/commands/run.js +6 -2
- package/dist/commands/secrets.js +120 -65
- package/dist/main.js +6 -0
- package/dist/utils/api-key-command-gate.d.ts +45 -0
- package/dist/utils/api-key-command-gate.js +51 -0
- package/dist/utils/hq-roots.d.ts +91 -0
- package/dist/utils/hq-roots.js +212 -0
- package/dist/utils/resolve-vault-credential.d.ts +63 -3
- package/dist/utils/resolve-vault-credential.js +152 -6
- package/dist/utils/run-bundled-script.d.ts +66 -0
- package/dist/utils/run-bundled-script.js +80 -0
- package/dist/utils/vault-api.d.ts +21 -0
- package/dist/utils/vault-api.js +74 -3
- package/package.json +7 -1
- package/.claude/policies/hq-cli-interactive-reads-use-sts-vend.md +0 -49
- package/.github/workflows/ci.yml +0 -46
- package/.github/workflows/publish.yml +0 -36
- package/.github/workflows/scripts/smoke-test-pkg.sh +0 -97
- package/e2e/cli.test.ts +0 -128
- package/eslint.config.js +0 -37
- package/pnpm-workspace.yaml +0 -4
- package/scripts/chmod-bins.mjs +0 -49
- package/scripts/generate-dsn.mjs +0 -26
- package/scripts/generate-scan-packages-table.mjs +0 -113
- package/scripts/smoke-sources-signals.sh +0 -103
- package/src/bin/hq-auth-refresh.ts +0 -43
- package/src/cli-version.ts +0 -13
- package/src/commands/__fixtures__/make-tar.ts +0 -126
- package/src/commands/_patterns.ts +0 -10
- package/src/commands/add.ts +0 -75
- package/src/commands/agents.test.ts +0 -527
- package/src/commands/agents.ts +0 -923
- package/src/commands/api-keys.test.ts +0 -291
- package/src/commands/api-keys.ts +0 -382
- package/src/commands/artifact-verify.test.ts +0 -177
- package/src/commands/auth.test.ts +0 -82
- package/src/commands/auth.ts +0 -213
- package/src/commands/billing.test.ts +0 -158
- package/src/commands/billing.ts +0 -146
- package/src/commands/channels.test.ts +0 -74
- package/src/commands/channels.ts +0 -91
- package/src/commands/cloud-demote.test.ts +0 -401
- package/src/commands/cloud-demote.ts +0 -278
- package/src/commands/cloud-provision.test.ts +0 -1117
- package/src/commands/cloud-provision.ts +0 -942
- package/src/commands/cloud.pull-all.test.ts +0 -711
- package/src/commands/cloud.pull-per-company.test.ts +0 -188
- package/src/commands/cloud.push-all.test.ts +0 -485
- package/src/commands/cloud.scope-excluded-warning.test.ts +0 -22
- package/src/commands/cloud.selectors.test.ts +0 -208
- package/src/commands/cloud.status.test.ts +0 -160
- package/src/commands/cloud.test.ts +0 -41
- package/src/commands/cloud.ts +0 -2130
- package/src/commands/company.test.ts +0 -202
- package/src/commands/company.ts +0 -148
- package/src/commands/creators.test.ts +0 -60
- package/src/commands/creators.ts +0 -117
- package/src/commands/crm.test.ts +0 -179
- package/src/commands/crm.ts +0 -236
- package/src/commands/db-migrate.ts +0 -55
- package/src/commands/db-provision.ts +0 -114
- package/src/commands/db-sql.ts +0 -124
- package/src/commands/db-status.ts +0 -108
- package/src/commands/db.ts +0 -26
- package/src/commands/dm.test.ts +0 -748
- package/src/commands/dm.ts +0 -1226
- package/src/commands/feedback.test.ts +0 -413
- package/src/commands/feedback.ts +0 -169
- package/src/commands/files-browse.test.ts +0 -1249
- package/src/commands/files-browse.ts +0 -1506
- package/src/commands/files-delete.test.ts +0 -484
- package/src/commands/files-recovery.test.ts +0 -370
- package/src/commands/files.test.ts +0 -722
- package/src/commands/files.ts +0 -1446
- package/src/commands/group-grants.test.ts +0 -330
- package/src/commands/group-grants.ts +0 -455
- package/src/commands/groups.test.ts +0 -44
- package/src/commands/groups.ts +0 -368
- package/src/commands/integrations.test.ts +0 -633
- package/src/commands/integrations.ts +0 -507
- package/src/commands/list.ts +0 -85
- package/src/commands/login.ts +0 -52
- package/src/commands/logout.ts +0 -30
- package/src/commands/marketplace-install.test.ts +0 -543
- package/src/commands/marketplace-security.test.ts +0 -646
- package/src/commands/mcp-registration.test.ts +0 -2787
- package/src/commands/mcp-registration.ts +0 -2612
- package/src/commands/mcp-status.test.ts +0 -483
- package/src/commands/mcp-status.ts +0 -575
- package/src/commands/mcp-status.us011.test.ts +0 -243
- package/src/commands/meetings.test.ts +0 -522
- package/src/commands/meetings.ts +0 -789
- package/src/commands/members.test.ts +0 -1474
- package/src/commands/members.ts +0 -1181
- package/src/commands/onboard-identity-guard.test.ts +0 -99
- package/src/commands/onboard-identity-guard.ts +0 -103
- package/src/commands/onboard-join.test.ts +0 -96
- package/src/commands/onboard-join.ts +0 -101
- package/src/commands/onboard-warning.test.ts +0 -26
- package/src/commands/onboard-warning.ts +0 -12
- package/src/commands/onboard.ts +0 -267
- package/src/commands/outposts-heartbeat.test.ts +0 -299
- package/src/commands/outposts-heartbeat.ts +0 -310
- package/src/commands/outposts-replica-sync.test.ts +0 -272
- package/src/commands/outposts-self-deploy.test.ts +0 -243
- package/src/commands/outposts.test.ts +0 -1039
- package/src/commands/outposts.ts +0 -1935
- package/src/commands/pack-install-secret-authorization.test.ts +0 -115
- package/src/commands/pack-install.test.ts +0 -1469
- package/src/commands/pack-install.ts +0 -2256
- package/src/commands/pack-update-cache.test.ts +0 -149
- package/src/commands/packs-update-api-key.test.ts +0 -105
- package/src/commands/packs.test.ts +0 -88
- package/src/commands/packs.ts +0 -622
- package/src/commands/people.test.ts +0 -807
- package/src/commands/people.ts +0 -422
- package/src/commands/pkg-install.dispatch.test.ts +0 -103
- package/src/commands/pkg-install.ts +0 -221
- package/src/commands/pkg-list.test.ts +0 -79
- package/src/commands/pkg-list.ts +0 -13
- package/src/commands/pkg-remove.ts +0 -71
- package/src/commands/pkg-update.ts +0 -189
- package/src/commands/publish.test.ts +0 -650
- package/src/commands/publish.ts +0 -580
- package/src/commands/reindex.test.ts +0 -259
- package/src/commands/reindex.ts +0 -274
- package/src/commands/rescue.test.ts +0 -39
- package/src/commands/rescue.ts +0 -210
- package/src/commands/run.env-local.test.ts +0 -88
- package/src/commands/run.test.ts +0 -45
- package/src/commands/run.ts +0 -190
- package/src/commands/safe-extract.test.ts +0 -459
- package/src/commands/safe-extract.ts +0 -444
- package/src/commands/secrets-scope.test.ts +0 -56
- package/src/commands/secrets-scope.ts +0 -32
- package/src/commands/secrets.parse-destination.test.ts +0 -38
- package/src/commands/secrets.test.ts +0 -2050
- package/src/commands/secrets.ts +0 -2249
- package/src/commands/signals.ts +0 -351
- package/src/commands/skill.test.ts +0 -770
- package/src/commands/skill.ts +0 -796
- package/src/commands/sources.ts +0 -362
- package/src/commands/sync-mode.test.ts +0 -366
- package/src/commands/sync-mode.ts +0 -387
- package/src/commands/sync-narrow.test.ts +0 -633
- package/src/commands/sync-narrow.ts +0 -555
- package/src/commands/sync.ts +0 -165
- package/src/commands/team-sync.ts +0 -628
- package/src/commands/update.ts +0 -71
- package/src/commands/whoami.ts +0 -83
- package/src/commands/workers.test.ts +0 -158
- package/src/commands/workers.ts +0 -301
- package/src/index.test.ts +0 -32
- package/src/index.ts +0 -20
- package/src/lib/db/control-plane.test.ts +0 -59
- package/src/lib/db/control-plane.ts +0 -113
- package/src/lib/db/local.test.ts +0 -81
- package/src/lib/db/local.ts +0 -148
- package/src/lib/db/migrate.test.ts +0 -133
- package/src/lib/db/migrate.ts +0 -137
- package/src/lib/db/paths.test.ts +0 -112
- package/src/lib/db/paths.ts +0 -128
- package/src/lib/db/remote-engine.test.ts +0 -44
- package/src/lib/db/remote-engine.ts +0 -148
- package/src/lib/db/remote-sql.test.ts +0 -32
- package/src/lib/db/remote-sql.ts +0 -62
- package/src/lib/db/sql.test.ts +0 -106
- package/src/lib/db/sql.ts +0 -192
- package/src/lib/hq-cloud-dep.smoke.test.ts +0 -75
- package/src/lib/local-tree-diff.test.ts +0 -301
- package/src/lib/local-tree-diff.ts +0 -362
- package/src/lib/narrow-hint-banner.test.ts +0 -235
- package/src/lib/narrow-hint-banner.ts +0 -212
- package/src/lib/plan-limit-nag.test.ts +0 -317
- package/src/lib/plan-limit-nag.ts +0 -264
- package/src/main.ts +0 -371
- package/src/node-preflight.test.ts +0 -60
- package/src/node-preflight.ts +0 -67
- package/src/outpost/session-heartbeat-bounds.test.ts +0 -195
- package/src/outpost/session-heartbeat-guard.test.ts +0 -105
- package/src/outpost/session-heartbeat-publisher.test.ts +0 -178
- package/src/outpost/session-heartbeat-publisher.ts +0 -186
- package/src/outpost/session-heartbeat-retain-guard.test.ts +0 -126
- package/src/outpost/session-heartbeat.test.ts +0 -459
- package/src/outpost/session-heartbeat.ts +0 -877
- package/src/packaging.test.ts +0 -109
- package/src/run/__fixtures__/discover-schemas/example.env.schema +0 -4
- package/src/run/discover-schemas.test.ts +0 -153
- package/src/run/discover-schemas.ts +0 -79
- package/src/run/hq-plugin.test.ts +0 -371
- package/src/run/hq-plugin.ts +0 -277
- package/src/run/varlock-shape.test.ts +0 -57
- package/src/schemas/hq-package.schema.json +0 -155
- package/src/sentry-before-send.test.ts +0 -359
- package/src/sentry-before-send.ts +0 -221
- package/src/sentry-epipe.test.ts +0 -37
- package/src/sentry-release.test.ts +0 -54
- package/src/sentry.test.ts +0 -24
- package/src/sentry.ts +0 -54
- package/src/strategies/link.ts +0 -62
- package/src/strategies/merge.ts +0 -142
- package/src/types.ts +0 -146
- package/src/utils/__fixtures__/scan-packages.generated-block.sh +0 -23
- package/src/utils/auth-error.test.ts +0 -40
- package/src/utils/auth-error.ts +0 -42
- package/src/utils/billing-gate.test.ts +0 -95
- package/src/utils/billing-gate.ts +0 -228
- package/src/utils/breadcrumb-buffer.ts +0 -18
- package/src/utils/cli-telemetry.test.ts +0 -153
- package/src/utils/cli-telemetry.ts +0 -61
- package/src/utils/cognito-session.machine.test.ts +0 -109
- package/src/utils/cognito-session.refresh.test.ts +0 -84
- package/src/utils/cognito-session.test.ts +0 -328
- package/src/utils/cognito-session.ts +0 -448
- package/src/utils/company-selection-error.test.ts +0 -42
- package/src/utils/company-selection-error.ts +0 -45
- package/src/utils/contribution-table.ts +0 -83
- package/src/utils/environmental-error.test.ts +0 -45
- package/src/utils/environmental-error.ts +0 -39
- package/src/utils/epipe.test.ts +0 -28
- package/src/utils/epipe.ts +0 -29
- package/src/utils/expected-cli-error.test.ts +0 -28
- package/src/utils/expected-cli-error.ts +0 -39
- package/src/utils/feedback-diagnostics.test.ts +0 -183
- package/src/utils/feedback-diagnostics.ts +0 -123
- package/src/utils/feedback-screenshots.test.ts +0 -134
- package/src/utils/feedback-screenshots.ts +0 -124
- package/src/utils/feedback-versions.test.ts +0 -98
- package/src/utils/feedback-versions.ts +0 -68
- package/src/utils/git.ts +0 -74
- package/src/utils/integrity.ts +0 -59
- package/src/utils/intercepted-process-exit.test.ts +0 -37
- package/src/utils/intercepted-process-exit.ts +0 -36
- package/src/utils/login-provider.test.ts +0 -42
- package/src/utils/login-provider.ts +0 -30
- package/src/utils/manifest.test.ts +0 -167
- package/src/utils/manifest.ts +0 -131
- package/src/utils/pack-contributions.test.ts +0 -603
- package/src/utils/pack-contributions.ts +0 -450
- package/src/utils/people.ts +0 -215
- package/src/utils/registry-client.ts +0 -204
- package/src/utils/registry.ts +0 -90
- package/src/utils/resolve-vault-credential.test.ts +0 -69
- package/src/utils/resolve-vault-credential.ts +0 -60
- package/src/utils/sandbox-runner-client.test.ts +0 -279
- package/src/utils/sandbox-runner-client.ts +0 -260
- package/src/utils/secrets-cache.ts +0 -183
- package/src/utils/sentry-identity.ts +0 -45
- package/src/utils/settle-with-timeout.test.ts +0 -21
- package/src/utils/settle-with-timeout.ts +0 -22
- package/src/utils/user-yaml-error.test.ts +0 -24
- package/src/utils/user-yaml-error.ts +0 -30
- package/src/utils/vault-api.test.ts +0 -536
- package/src/utils/vault-api.ts +0 -382
- package/src/utils/version-check.test.ts +0 -176
- package/src/utils/version-check.ts +0 -155
- package/src/utils/version-gate.test.ts +0 -1024
- package/src/utils/version-gate.ts +0 -702
- package/test/commands/db-tenant-isolation.test.ts +0 -94
- package/test/commands/db.test.ts +0 -85
- package/test/commands/signals.test.ts +0 -207
- package/test/commands/sources.test.ts +0 -231
- package/test/e2e/smoke-install-mcp.sh +0 -113
- package/test/fixtures/hq-pack-smoke-mcp/mcp/smoke-http.json +0 -1
- package/test/fixtures/hq-pack-smoke-mcp/package.yaml +0 -11
- package/test/fixtures/signals/action_item/sample.md +0 -16
- package/test/fixtures/signals/summary/sample.md +0 -12
- package/test/fixtures/sources/meetings/sample.md +0 -25
- package/test/helpers/cli-runner.ts +0 -150
- package/test/helpers/s3-list-mock.ts +0 -79
- package/test/helpers/vault-service-mock.ts +0 -295
- package/test/sources-signals/smoke.test.ts +0 -228
- package/tsconfig.json +0 -20
- package/vitest.config.ts +0 -11
- package/vitest.e2e.config.ts +0 -7
|
@@ -1,543 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* US-006 — `hq install marketplace:<slug>` transport.
|
|
3
|
-
*
|
|
4
|
-
* All network is mocked via injected `MarketplaceDeps` (resolveListing /
|
|
5
|
-
* refreshListing / download); the only real I/O is the local filesystem
|
|
6
|
-
* (tarball creation, safe-extract, install + symlink wiring) under per-test
|
|
7
|
-
* tmp dirs. These tests never hit the real listings API or S3.
|
|
8
|
-
*
|
|
9
|
-
* E2E behaviors covered:
|
|
10
|
-
* - Approved listing → `hq install marketplace:<slug>` installs; contributes
|
|
11
|
-
* (skills/workers/knowledge) appear under core/packages/<name>/.
|
|
12
|
-
* - Pack declaring hooks → the hook-consent gate fires before wiring.
|
|
13
|
-
* - Installed marketplace pack → `hq packs update` (resolveLatestMarketplace)
|
|
14
|
-
* checks for a newer listing version.
|
|
15
|
-
* - Tarball whose bytes don't match the approved hash → refuses, wires
|
|
16
|
-
* nothing (ArtifactVerificationError, no core/packages/<name>/).
|
|
17
|
-
* - Expired presigned URL → re-resolve once and retry; raw S3 403 never leaks.
|
|
18
|
-
* - REGRESSION: legacy/local transport still classifies + installs unchanged
|
|
19
|
-
* after adding the marketplace transport.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
23
|
-
import * as fs from 'node:fs';
|
|
24
|
-
import * as os from 'node:os';
|
|
25
|
-
import * as path from 'node:path';
|
|
26
|
-
import { execFileSync } from 'node:child_process';
|
|
27
|
-
import { createHash } from 'node:crypto';
|
|
28
|
-
|
|
29
|
-
// Mock readline so the hook-consent prompt is non-interactive. `consentAnswer`
|
|
30
|
-
// is read at prompt time, so each test can set it before installing. ESM
|
|
31
|
-
// disallows spying on the live `createInterface` export, so we replace the
|
|
32
|
-
// module instead.
|
|
33
|
-
let consentAnswer = 'n';
|
|
34
|
-
vi.mock('node:readline', () => ({
|
|
35
|
-
createInterface: () => ({
|
|
36
|
-
question: (_q: string, cb: (a: string) => void) => cb(consentAnswer),
|
|
37
|
-
close: () => undefined,
|
|
38
|
-
}),
|
|
39
|
-
}));
|
|
40
|
-
|
|
41
|
-
// Mock the public listings API so `defaultMarketplaceDeps().resolveListing` is
|
|
42
|
-
// exercised against a controllable feed (BUG-1 exact-slug-match regression).
|
|
43
|
-
// `publicResponder` is read at call time so each test can shape the response —
|
|
44
|
-
// it receives the call options + a zero-based call index so a test can return a
|
|
45
|
-
// different body for the browse call vs. the follow-up detail fetch.
|
|
46
|
-
type PublicResponse = { ok: boolean; status: number; body: unknown };
|
|
47
|
-
let publicResponder: (
|
|
48
|
-
opts: { path: string; query?: Record<string, string> },
|
|
49
|
-
call: number,
|
|
50
|
-
) => PublicResponse = () => ({ ok: true, status: 200, body: { listings: [] } });
|
|
51
|
-
const fetchPublicCalls: Array<{ path: string; query?: Record<string, string> }> = [];
|
|
52
|
-
vi.mock('../utils/vault-api.js', () => ({
|
|
53
|
-
vaultApiFetchPublic: async (opts: { path: string; query?: Record<string, string> }) => {
|
|
54
|
-
const call = fetchPublicCalls.length;
|
|
55
|
-
fetchPublicCalls.push(opts);
|
|
56
|
-
const r = publicResponder(opts, call);
|
|
57
|
-
return {
|
|
58
|
-
ok: r.ok,
|
|
59
|
-
status: r.status,
|
|
60
|
-
json: async () => r.body,
|
|
61
|
-
} as unknown as Response;
|
|
62
|
-
},
|
|
63
|
-
}));
|
|
64
|
-
|
|
65
|
-
import {
|
|
66
|
-
classify,
|
|
67
|
-
parseMarketplaceSource,
|
|
68
|
-
toMarketplaceListing,
|
|
69
|
-
fetchMarketplace,
|
|
70
|
-
resolveLatestMarketplace,
|
|
71
|
-
installPack,
|
|
72
|
-
defaultMarketplaceDeps,
|
|
73
|
-
ArtifactVerificationError,
|
|
74
|
-
type MarketplaceDeps,
|
|
75
|
-
type MarketplaceListing,
|
|
76
|
-
} from './pack-install.js';
|
|
77
|
-
|
|
78
|
-
// ---------------------------------------------------------------------------
|
|
79
|
-
// Fixtures
|
|
80
|
-
// ---------------------------------------------------------------------------
|
|
81
|
-
|
|
82
|
-
/** A minimal, valid pack payload that validateManifest accepts. */
|
|
83
|
-
function writePackPayload(
|
|
84
|
-
root: string,
|
|
85
|
-
opts: { name?: string; version?: string; withHooks?: boolean } = {},
|
|
86
|
-
): string {
|
|
87
|
-
const name = opts.name ?? 'hq-pack-demo';
|
|
88
|
-
const version = opts.version ?? '1.0.0';
|
|
89
|
-
const dir = path.join(root, 'payload');
|
|
90
|
-
fs.mkdirSync(path.join(dir, 'skills', 'demo'), { recursive: true });
|
|
91
|
-
fs.writeFileSync(path.join(dir, 'skills', 'demo', 'SKILL.md'), '# demo skill\n');
|
|
92
|
-
const contributes: string[] = ['contributes:', ' skills:', ' - demo'];
|
|
93
|
-
if (opts.withHooks) {
|
|
94
|
-
fs.mkdirSync(path.join(dir, 'hooks'), { recursive: true });
|
|
95
|
-
fs.writeFileSync(path.join(dir, 'hooks', 'pre.sh'), '#!/bin/sh\necho hi\n');
|
|
96
|
-
contributes.push(' hooks:', ' - pre');
|
|
97
|
-
}
|
|
98
|
-
fs.writeFileSync(
|
|
99
|
-
path.join(dir, 'package.yaml'),
|
|
100
|
-
[
|
|
101
|
-
`name: ${name}`,
|
|
102
|
-
`version: ${version}`,
|
|
103
|
-
"publisher: '@indigoai-us'",
|
|
104
|
-
'access: public',
|
|
105
|
-
'requires:',
|
|
106
|
-
" hqCore: '>=1.0.0'",
|
|
107
|
-
...contributes,
|
|
108
|
-
'',
|
|
109
|
-
].join('\n'),
|
|
110
|
-
);
|
|
111
|
-
return dir;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/** tar -czf payloadDir contents at archive root (matches publish.ts packing). */
|
|
115
|
-
function tarballOf(payloadDir: string): Uint8Array {
|
|
116
|
-
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'mkt-tar-'));
|
|
117
|
-
const tarPath = path.join(tmp, 'pack.tar.gz');
|
|
118
|
-
try {
|
|
119
|
-
execFileSync(
|
|
120
|
-
'tar',
|
|
121
|
-
['-czf', tarPath, '-C', payloadDir, '--exclude=.DS_Store', '.'],
|
|
122
|
-
{ stdio: ['ignore', 'ignore', 'inherit'] },
|
|
123
|
-
);
|
|
124
|
-
return fs.readFileSync(tarPath);
|
|
125
|
-
} finally {
|
|
126
|
-
fs.rmSync(tmp, { recursive: true, force: true });
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function sha256Hex(bytes: Uint8Array): string {
|
|
131
|
-
return createHash('sha256').update(bytes).digest('hex');
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/** Build injected deps that serve `bytes` for `slug` with the matching hash. */
|
|
135
|
-
function makeDeps(
|
|
136
|
-
slug: string,
|
|
137
|
-
bytes: Uint8Array,
|
|
138
|
-
opts: {
|
|
139
|
-
version?: string;
|
|
140
|
-
contentHash?: string; // override to simulate tamper
|
|
141
|
-
latestVersion?: string; // for update probe
|
|
142
|
-
expireFirstDownload?: boolean;
|
|
143
|
-
} = {},
|
|
144
|
-
): { deps: MarketplaceDeps; calls: { resolve: number; refresh: number; download: number } } {
|
|
145
|
-
const calls = { resolve: 0, refresh: 0, download: 0 };
|
|
146
|
-
const detail: MarketplaceListing = {
|
|
147
|
-
listingId: `lst_${slug}`,
|
|
148
|
-
slug,
|
|
149
|
-
version: opts.version ?? '1.0.0',
|
|
150
|
-
downloadUrl: 'https://s3.example/presigned?sig=abc',
|
|
151
|
-
contentHash: opts.contentHash ?? sha256Hex(bytes),
|
|
152
|
-
};
|
|
153
|
-
let firstDownload = true;
|
|
154
|
-
return {
|
|
155
|
-
calls,
|
|
156
|
-
deps: {
|
|
157
|
-
resolveListing: async (s, v) => {
|
|
158
|
-
calls.resolve++;
|
|
159
|
-
expect(s).toBe(slug);
|
|
160
|
-
// For the update probe we resolve with NO version → report latest.
|
|
161
|
-
if (v === undefined && opts.latestVersion) {
|
|
162
|
-
return { ...detail, version: opts.latestVersion };
|
|
163
|
-
}
|
|
164
|
-
return { ...detail };
|
|
165
|
-
},
|
|
166
|
-
refreshListing: async (id) => {
|
|
167
|
-
calls.refresh++;
|
|
168
|
-
expect(id).toBe(detail.listingId);
|
|
169
|
-
return { ...detail, downloadUrl: 'https://s3.example/presigned?sig=fresh' };
|
|
170
|
-
},
|
|
171
|
-
download: async (_url) => {
|
|
172
|
-
calls.download++;
|
|
173
|
-
if (opts.expireFirstDownload && firstDownload) {
|
|
174
|
-
firstDownload = false;
|
|
175
|
-
return { expired: true };
|
|
176
|
-
}
|
|
177
|
-
return bytes;
|
|
178
|
-
},
|
|
179
|
-
},
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/** A fake HQ root with `.claude/` so findHqRoot() resolves to it. */
|
|
184
|
-
function mkFakeHq(): string {
|
|
185
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'mkt-hq-'));
|
|
186
|
-
fs.mkdirSync(path.join(root, '.claude'), { recursive: true });
|
|
187
|
-
fs.writeFileSync(path.join(root, 'core.yaml'), 'hqVersion: 99.0.0\n');
|
|
188
|
-
return root;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// ---------------------------------------------------------------------------
|
|
192
|
-
// parse + classify
|
|
193
|
-
// ---------------------------------------------------------------------------
|
|
194
|
-
|
|
195
|
-
describe('US-006 marketplace: source parsing + classification', () => {
|
|
196
|
-
it('classify recognizes marketplace: BEFORE every other transport', () => {
|
|
197
|
-
expect(classify('marketplace:my-pack')).toBe('marketplace');
|
|
198
|
-
expect(classify('marketplace:my-pack@1.2.3')).toBe('marketplace');
|
|
199
|
-
// A marketplace source must never be misread as npm/git/local.
|
|
200
|
-
expect(classify('@scope/name')).toBe('npm');
|
|
201
|
-
expect(classify('./local')).toBe('local');
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it('parseMarketplaceSource splits slug and optional version', () => {
|
|
205
|
-
expect(parseMarketplaceSource('marketplace:foo')).toEqual({ slug: 'foo' });
|
|
206
|
-
expect(parseMarketplaceSource('marketplace:foo@2.0.0')).toEqual({
|
|
207
|
-
slug: 'foo',
|
|
208
|
-
version: '2.0.0',
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it('parseMarketplaceSource rejects an empty slug', () => {
|
|
213
|
-
expect(() => parseMarketplaceSource('marketplace:')).toThrow(/requires a slug/);
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
it('toMarketplaceListing refuses a listing with no content hash', () => {
|
|
217
|
-
expect(() =>
|
|
218
|
-
toMarketplaceListing({ id: 'lst_x', downloadUrl: 'https://s3/x' }),
|
|
219
|
-
).toThrow(/no content hash/);
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
// ---------------------------------------------------------------------------
|
|
224
|
-
// fetchMarketplace — verify-before-extract
|
|
225
|
-
// ---------------------------------------------------------------------------
|
|
226
|
-
|
|
227
|
-
describe('US-006 marketplace: fetchMarketplace', () => {
|
|
228
|
-
let hq: string;
|
|
229
|
-
let tmpDir: string;
|
|
230
|
-
|
|
231
|
-
beforeEach(() => {
|
|
232
|
-
hq = mkFakeHq();
|
|
233
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mkt-fetch-'));
|
|
234
|
-
});
|
|
235
|
-
afterEach(() => {
|
|
236
|
-
fs.rmSync(hq, { recursive: true, force: true });
|
|
237
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
it('downloads, verifies hash, and safe-extracts to a payloadDir with package.yaml', async () => {
|
|
241
|
-
const payload = writePackPayload(hq);
|
|
242
|
-
const bytes = tarballOf(payload);
|
|
243
|
-
const { deps } = makeDeps('demo', bytes, { version: '1.0.0' });
|
|
244
|
-
|
|
245
|
-
const result = await fetchMarketplace('marketplace:demo', tmpDir, deps);
|
|
246
|
-
|
|
247
|
-
expect(fs.existsSync(path.join(result.payloadDir, 'package.yaml'))).toBe(true);
|
|
248
|
-
expect(fs.existsSync(path.join(result.payloadDir, 'skills', 'demo', 'SKILL.md'))).toBe(
|
|
249
|
-
true,
|
|
250
|
-
);
|
|
251
|
-
// Resolved source records the marketplace version for re-install/update.
|
|
252
|
-
expect(result.resolvedSource).toBe('marketplace:demo@1.0.0');
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
it('REFUSES + extracts nothing when the bytes do not match the approved hash', async () => {
|
|
256
|
-
const payload = writePackPayload(hq);
|
|
257
|
-
const bytes = tarballOf(payload);
|
|
258
|
-
// Listing pins a DIFFERENT hash than the bytes we serve (tamper).
|
|
259
|
-
const wrongHash = 'a'.repeat(64);
|
|
260
|
-
const { deps } = makeDeps('demo', bytes, { contentHash: wrongHash });
|
|
261
|
-
|
|
262
|
-
await expect(fetchMarketplace('marketplace:demo', tmpDir, deps)).rejects.toThrow(
|
|
263
|
-
ArtifactVerificationError,
|
|
264
|
-
);
|
|
265
|
-
// No extract dir was produced — nothing unpacked.
|
|
266
|
-
expect(fs.existsSync(path.join(tmpDir, 'extracted'))).toBe(false);
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
it('re-resolves a FRESH presigned URL on an expired (403) download, then succeeds', async () => {
|
|
270
|
-
const payload = writePackPayload(hq);
|
|
271
|
-
const bytes = tarballOf(payload);
|
|
272
|
-
const { deps, calls } = makeDeps('demo', bytes, { expireFirstDownload: true });
|
|
273
|
-
|
|
274
|
-
const result = await fetchMarketplace('marketplace:demo', tmpDir, deps);
|
|
275
|
-
|
|
276
|
-
expect(fs.existsSync(path.join(result.payloadDir, 'package.yaml'))).toBe(true);
|
|
277
|
-
expect(calls.refresh).toBe(1); // re-resolved once
|
|
278
|
-
expect(calls.download).toBe(2); // first 403, second OK
|
|
279
|
-
});
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
// ---------------------------------------------------------------------------
|
|
283
|
-
// installPack — full E2E through the existing local-install path
|
|
284
|
-
// ---------------------------------------------------------------------------
|
|
285
|
-
|
|
286
|
-
describe('US-006 marketplace: installPack E2E', () => {
|
|
287
|
-
let hq: string;
|
|
288
|
-
let prevCwd: string;
|
|
289
|
-
|
|
290
|
-
beforeEach(() => {
|
|
291
|
-
hq = mkFakeHq();
|
|
292
|
-
prevCwd = process.cwd();
|
|
293
|
-
process.chdir(hq); // findHqRoot() resolves to hq via .claude/
|
|
294
|
-
});
|
|
295
|
-
afterEach(() => {
|
|
296
|
-
process.chdir(prevCwd);
|
|
297
|
-
fs.rmSync(hq, { recursive: true, force: true });
|
|
298
|
-
vi.restoreAllMocks();
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
it('approved listing → installs; contributes land under core/packages/<name>/ and source is stamped', async () => {
|
|
302
|
-
const payload = writePackPayload(hq, { name: 'hq-pack-demo', version: '1.0.0' });
|
|
303
|
-
const bytes = tarballOf(payload);
|
|
304
|
-
const { deps } = makeDeps('demo', bytes, { version: '1.0.0' });
|
|
305
|
-
vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
|
306
|
-
|
|
307
|
-
await installPack('marketplace:demo', { marketplaceDeps: deps });
|
|
308
|
-
|
|
309
|
-
const dest = path.join(hq, 'core', 'packages', 'hq-pack-demo');
|
|
310
|
-
expect(fs.existsSync(path.join(dest, 'skills', 'demo', 'SKILL.md'))).toBe(true);
|
|
311
|
-
// modules.yaml provenance equivalent: the resolved marketplace source is
|
|
312
|
-
// stamped into the installed package.yaml so re-install + packs update work.
|
|
313
|
-
const stamped = fs.readFileSync(path.join(dest, 'package.yaml'), 'utf-8');
|
|
314
|
-
expect(stamped.split('\n')[0]).toBe('source: "marketplace:demo@1.0.0"');
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
it('pack declaring hooks → hook-consent gate fires BEFORE wiring (declines → nothing installed)', async () => {
|
|
318
|
-
const payload = writePackPayload(hq, { name: 'hq-pack-hooked', withHooks: true });
|
|
319
|
-
const bytes = tarballOf(payload);
|
|
320
|
-
const { deps } = makeDeps('hooked', bytes);
|
|
321
|
-
vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
|
322
|
-
|
|
323
|
-
// Consent prompt DECLINES (simulates the gate firing).
|
|
324
|
-
consentAnswer = 'n';
|
|
325
|
-
await installPack('marketplace:hooked', { marketplaceDeps: deps });
|
|
326
|
-
|
|
327
|
-
// Declined at the consent gate → pack must NOT be wired.
|
|
328
|
-
expect(fs.existsSync(path.join(hq, 'core', 'packages', 'hq-pack-hooked'))).toBe(false);
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
it('pack declaring hooks → consent accepted installs the pack (gate not bypassed)', async () => {
|
|
332
|
-
const payload = writePackPayload(hq, { name: 'hq-pack-hooked', withHooks: true });
|
|
333
|
-
const bytes = tarballOf(payload);
|
|
334
|
-
const { deps } = makeDeps('hooked', bytes);
|
|
335
|
-
vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
|
336
|
-
|
|
337
|
-
// Consent prompt ACCEPTS — gate fired and was approved (not bypassed).
|
|
338
|
-
consentAnswer = 'y';
|
|
339
|
-
await installPack('marketplace:hooked', { marketplaceDeps: deps });
|
|
340
|
-
|
|
341
|
-
expect(fs.existsSync(path.join(hq, 'core', 'packages', 'hq-pack-hooked'))).toBe(true);
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
it('tampered bytes (hash mismatch) → installPack refuses; nothing wired', async () => {
|
|
345
|
-
const payload = writePackPayload(hq, { name: 'hq-pack-demo' });
|
|
346
|
-
const bytes = tarballOf(payload);
|
|
347
|
-
const { deps } = makeDeps('demo', bytes, { contentHash: 'b'.repeat(64) });
|
|
348
|
-
vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
|
349
|
-
|
|
350
|
-
await expect(
|
|
351
|
-
installPack('marketplace:demo', { marketplaceDeps: deps }),
|
|
352
|
-
).rejects.toThrow(ArtifactVerificationError);
|
|
353
|
-
|
|
354
|
-
expect(fs.existsSync(path.join(hq, 'core', 'packages', 'hq-pack-demo'))).toBe(false);
|
|
355
|
-
});
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
// ---------------------------------------------------------------------------
|
|
359
|
-
// hq packs update — async marketplace probe
|
|
360
|
-
// ---------------------------------------------------------------------------
|
|
361
|
-
|
|
362
|
-
describe('US-006 marketplace: update probe', () => {
|
|
363
|
-
it('reports a newer listing version as updateAvailable', async () => {
|
|
364
|
-
const dummy = new Uint8Array([1, 2, 3]);
|
|
365
|
-
const { deps } = makeDeps('demo', dummy, { latestVersion: '2.0.0' });
|
|
366
|
-
|
|
367
|
-
const r = await resolveLatestMarketplace('marketplace:demo@1.0.0', '1.0.0', deps);
|
|
368
|
-
|
|
369
|
-
expect(r.transport).toBe('marketplace');
|
|
370
|
-
expect(r.current).toBe('1.0.0');
|
|
371
|
-
expect(r.latest).toBe('2.0.0');
|
|
372
|
-
expect(r.updateAvailable).toBe(true);
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
it('reports no update when the listing version matches', async () => {
|
|
376
|
-
const dummy = new Uint8Array([1, 2, 3]);
|
|
377
|
-
const { deps } = makeDeps('demo', dummy, { latestVersion: '1.0.0' });
|
|
378
|
-
|
|
379
|
-
const r = await resolveLatestMarketplace('marketplace:demo@1.0.0', '1.0.0', deps);
|
|
380
|
-
|
|
381
|
-
expect(r.updateAvailable).toBe(false);
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
it('never throws on a network failure — returns updateAvailable:null + error', async () => {
|
|
385
|
-
const deps: MarketplaceDeps = {
|
|
386
|
-
resolveListing: async () => {
|
|
387
|
-
throw new Error('network down');
|
|
388
|
-
},
|
|
389
|
-
refreshListing: async () => {
|
|
390
|
-
throw new Error('network down');
|
|
391
|
-
},
|
|
392
|
-
download: async () => new Uint8Array(),
|
|
393
|
-
};
|
|
394
|
-
|
|
395
|
-
const r = await resolveLatestMarketplace('marketplace:demo@1.0.0', '1.0.0', deps);
|
|
396
|
-
|
|
397
|
-
expect(r.updateAvailable).toBeNull();
|
|
398
|
-
expect(r.error).toMatch(/network down/);
|
|
399
|
-
});
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
// ---------------------------------------------------------------------------
|
|
403
|
-
// REGRESSION — legacy transports unchanged by adding marketplace
|
|
404
|
-
// ---------------------------------------------------------------------------
|
|
405
|
-
|
|
406
|
-
describe('US-006 REGRESSION: legacy transports still dispatch + install unchanged', () => {
|
|
407
|
-
it('classify still routes npm / git / local exactly as before', () => {
|
|
408
|
-
expect(classify('@scope/name@1.0.0')).toBe('npm');
|
|
409
|
-
expect(classify('https://github.com/o/r.git')).toBe('git');
|
|
410
|
-
expect(classify('github:owner/repo')).toBe('git');
|
|
411
|
-
expect(classify('git@github.com:o/r.git')).toBe('git');
|
|
412
|
-
expect(classify('./path')).toBe('local');
|
|
413
|
-
expect(classify('/abs/path')).toBe('local');
|
|
414
|
-
expect(classify('file:./x')).toBe('local');
|
|
415
|
-
// Bare slug still rejected by classify → routed to the legacy registry flow.
|
|
416
|
-
expect(() => classify('bare-slug')).toThrow(/Bare slugs go through the registry/);
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
it('local transport still installs end-to-end (no marketplace deps involved)', async () => {
|
|
420
|
-
const hq = mkFakeHq();
|
|
421
|
-
const payload = writePackPayload(hq, { name: 'hq-pack-local' });
|
|
422
|
-
const prevCwd = process.cwd();
|
|
423
|
-
process.chdir(hq);
|
|
424
|
-
vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
|
425
|
-
try {
|
|
426
|
-
// Point installPack at the local payload dir — the legacy local path.
|
|
427
|
-
await installPack(payload);
|
|
428
|
-
const dest = path.join(hq, 'core', 'packages', 'hq-pack-local');
|
|
429
|
-
expect(fs.existsSync(path.join(dest, 'skills', 'demo', 'SKILL.md'))).toBe(true);
|
|
430
|
-
// Local source stamped verbatim (NOT rewritten as a marketplace source).
|
|
431
|
-
const stamped = fs.readFileSync(path.join(dest, 'package.yaml'), 'utf-8');
|
|
432
|
-
expect(stamped.split('\n')[0]).toBe(`source: "${payload}"`);
|
|
433
|
-
} finally {
|
|
434
|
-
process.chdir(prevCwd);
|
|
435
|
-
fs.rmSync(hq, { recursive: true, force: true });
|
|
436
|
-
vi.restoreAllMocks();
|
|
437
|
-
}
|
|
438
|
-
});
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
// ---------------------------------------------------------------------------
|
|
442
|
-
// BUG-1 — defaultMarketplaceDeps().resolveListing must EXACT-slug-match
|
|
443
|
-
//
|
|
444
|
-
// The browse handler historically ignored `?slug=` and only honored `?q=`
|
|
445
|
-
// (fuzzy), so `?slug=tdd` returned ALL approved listings newest-first. The old
|
|
446
|
-
// code took `listings[0]` → installed the NEWEST pack, not the requested slug
|
|
447
|
-
// (verified live: `marketplace:tdd` installed hq-pack-review). resolveListing
|
|
448
|
-
// must now exact-match `l.slug === slug` and throw when no exact match exists,
|
|
449
|
-
// and must query with BOTH `slug` + `q` for forward/back compatibility.
|
|
450
|
-
// ---------------------------------------------------------------------------
|
|
451
|
-
|
|
452
|
-
describe('BUG-1 resolveListing exact-slug match (defaultMarketplaceDeps)', () => {
|
|
453
|
-
beforeEach(() => {
|
|
454
|
-
fetchPublicCalls.length = 0;
|
|
455
|
-
publicResponder = () => ({ ok: true, status: 200, body: { listings: [] } });
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
/** A browse-summary entry (id present, no URL — detail fetch mints it). */
|
|
459
|
-
function summary(slug: string, version: string, extra?: Record<string, unknown>) {
|
|
460
|
-
return { listingId: `lst_${slug}`, slug, version, status: 'approved', ...extra };
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
/** The `GET /v1/listings/{id}` detail the follow-up fetch returns. */
|
|
464
|
-
function detailBody(slug: string, version: string) {
|
|
465
|
-
return {
|
|
466
|
-
listing: {
|
|
467
|
-
id: `lst_${slug}`,
|
|
468
|
-
slug,
|
|
469
|
-
version,
|
|
470
|
-
downloadUrl: 'https://s3.example/presigned?sig=abc',
|
|
471
|
-
contentHash: 'a'.repeat(64),
|
|
472
|
-
},
|
|
473
|
-
};
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
it('returns the EXACTLY-slugged listing — NOT listings[0] — from a multi-pack feed', async () => {
|
|
477
|
-
// Server returns a slug-ignoring fuzzy feed newest-first: review is [0], the
|
|
478
|
-
// requested tdd is later. Old code returned review; the fix returns tdd.
|
|
479
|
-
publicResponder = (_opts, call) =>
|
|
480
|
-
call === 0
|
|
481
|
-
? {
|
|
482
|
-
ok: true,
|
|
483
|
-
status: 200,
|
|
484
|
-
body: { listings: [summary('review', '3.0.0'), summary('tdd', '1.2.0')] },
|
|
485
|
-
}
|
|
486
|
-
: { ok: true, status: 200, body: detailBody('tdd', '1.2.0') };
|
|
487
|
-
|
|
488
|
-
const listing = await defaultMarketplaceDeps().resolveListing('tdd');
|
|
489
|
-
|
|
490
|
-
expect(listing.slug).toBe('tdd');
|
|
491
|
-
expect(listing.listingId).toBe('lst_tdd');
|
|
492
|
-
// The browse query carried BOTH slug (exact) and q (fuzzy fallback).
|
|
493
|
-
expect(fetchPublicCalls[0].query).toMatchObject({ slug: 'tdd', q: 'tdd' });
|
|
494
|
-
});
|
|
495
|
-
|
|
496
|
-
it('throws a clear error when NO exact-slug match exists (never installs a near-miss)', async () => {
|
|
497
|
-
// Feed contains only OTHER slugs — a slug-ignoring/fuzzy server response.
|
|
498
|
-
publicResponder = () => ({
|
|
499
|
-
ok: true,
|
|
500
|
-
status: 200,
|
|
501
|
-
body: { listings: [summary('review', '3.0.0'), summary('tdd-helper', '1.0.0')] },
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
await expect(defaultMarketplaceDeps().resolveListing('tdd')).rejects.toThrow(
|
|
505
|
-
/No marketplace listing found for slug "tdd"/,
|
|
506
|
-
);
|
|
507
|
-
});
|
|
508
|
-
|
|
509
|
-
it('with a pinned version, requires BOTH exact slug AND exact version', async () => {
|
|
510
|
-
publicResponder = (_opts, call) =>
|
|
511
|
-
call === 0
|
|
512
|
-
? {
|
|
513
|
-
ok: true,
|
|
514
|
-
status: 200,
|
|
515
|
-
body: {
|
|
516
|
-
listings: [
|
|
517
|
-
summary('tdd', '2.0.0'),
|
|
518
|
-
summary('tdd', '1.5.0'),
|
|
519
|
-
summary('review', '9.9.9'),
|
|
520
|
-
],
|
|
521
|
-
},
|
|
522
|
-
}
|
|
523
|
-
: { ok: true, status: 200, body: detailBody('tdd', '1.5.0') };
|
|
524
|
-
|
|
525
|
-
const listing = await defaultMarketplaceDeps().resolveListing('tdd', '1.5.0');
|
|
526
|
-
|
|
527
|
-
expect(listing.slug).toBe('tdd');
|
|
528
|
-
expect(listing.version).toBe('1.5.0');
|
|
529
|
-
expect(fetchPublicCalls[0].query).toMatchObject({ slug: 'tdd', q: 'tdd', version: '1.5.0' });
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
it('throws when the pinned version is absent even if the slug matches', async () => {
|
|
533
|
-
publicResponder = () => ({
|
|
534
|
-
ok: true,
|
|
535
|
-
status: 200,
|
|
536
|
-
body: { listings: [summary('tdd', '2.0.0'), summary('tdd', '1.5.0')] },
|
|
537
|
-
});
|
|
538
|
-
|
|
539
|
-
await expect(defaultMarketplaceDeps().resolveListing('tdd', '9.9.9')).rejects.toThrow(
|
|
540
|
-
/No marketplace listing found for slug "tdd"@9\.9\.9/,
|
|
541
|
-
);
|
|
542
|
-
});
|
|
543
|
-
});
|