@canonical/pragma-cli 0.29.0 → 0.30.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/README.md +67 -15
- package/dist/pragma +0 -0
- package/package.json +13 -13
- package/src/config/defaultConfig.test.ts +33 -0
- package/src/config/defaultConfig.ts +59 -0
- package/src/config/defaults.json +17 -0
- package/src/config/findProjectConfigPath.test.ts +53 -0
- package/src/config/findProjectConfigPath.ts +36 -0
- package/src/config/index.ts +17 -2
- package/src/config/mergeConfigFileUpdate.ts +58 -0
- package/src/config/parseConfigValues.ts +215 -0
- package/src/config/readConfig.test.ts +104 -13
- package/src/config/readConfig.ts +12 -153
- package/src/config/readConfigLayers.test.ts +107 -0
- package/src/config/readConfigLayers.ts +115 -0
- package/src/config/resolveGlobalConfigPath.ts +13 -0
- package/src/config/resolveWriteConfigPath.test.ts +53 -0
- package/src/config/resolveWriteConfigPath.ts +31 -0
- package/src/config/types.ts +76 -1
- package/src/config/writeConfig.ts +25 -55
- package/src/domains/block/commands/list.ts +5 -90
- package/src/domains/block/commands/lookup.ts +8 -167
- package/src/domains/block/mcp/specs.ts +16 -196
- package/src/domains/block/orchestration/resolveBlockLookup.ts +2 -1
- package/src/domains/block/stories.test.ts +28 -0
- package/src/domains/block/stories.ts +254 -0
- package/src/domains/config/commands/channel.test.ts +14 -3
- package/src/domains/config/commands/channel.ts +11 -7
- package/src/domains/config/commands/configScope.ts +46 -0
- package/src/domains/config/commands/framework.test.ts +18 -1
- package/src/domains/config/commands/framework.ts +13 -7
- package/src/domains/config/commands/show.ts +5 -45
- package/src/domains/config/commands/tier.test.ts +13 -2
- package/src/domains/config/commands/tier.ts +11 -7
- package/src/domains/config/commands/trace.test.ts +19 -2
- package/src/domains/config/commands/trace.ts +6 -3
- package/src/domains/config/formatters/channel.test.ts +20 -6
- package/src/domains/config/formatters/channel.ts +14 -11
- package/src/domains/config/formatters/framework.ts +14 -11
- package/src/domains/config/formatters/show.test.ts +27 -0
- package/src/domains/config/formatters/show.ts +27 -4
- package/src/domains/config/formatters/tier.test.ts +26 -10
- package/src/domains/config/formatters/tier.ts +19 -11
- package/src/domains/config/formatters/trace.ts +7 -7
- package/src/domains/config/mcp/specs.ts +33 -53
- package/src/domains/config/operations/show.test.ts +18 -0
- package/src/domains/config/operations/show.ts +7 -1
- package/src/domains/config/operations/types.ts +7 -0
- package/src/domains/config/stories.ts +42 -0
- package/src/domains/config/tasks/index.ts +1 -0
- package/src/domains/config/tasks/setChannel.ts +13 -28
- package/src/domains/config/tasks/setFramework.ts +11 -30
- package/src/domains/config/tasks/setTier.ts +10 -29
- package/src/domains/config/tasks/setTrace.ts +10 -28
- package/src/domains/config/tasks/writeConfigField.ts +41 -0
- package/src/domains/create/commands/component.ts +5 -5
- package/src/domains/doctor/operations/checks/checkConfigFile.ts +19 -5
- package/src/domains/doctor/operations/checks/checkMcpCommands.ts +130 -0
- package/src/domains/doctor/operations/checks/checkMcpConfigured.ts +1 -1
- package/src/domains/doctor/operations/checks/checkPackageRefs.ts +2 -14
- package/src/domains/doctor/operations/checks/checkSkillsSymlinked.ts +1 -1
- package/src/domains/doctor/operations/checks/index.ts +1 -0
- package/src/domains/doctor/operations/checks.test.ts +158 -7
- package/src/domains/doctor/operations/runChecks.test.ts +15 -6
- package/src/domains/doctor/operations/runChecks.ts +2 -0
- package/src/domains/graph/helpers/resolveUri.test.ts +50 -0
- package/src/domains/graph/helpers/resolveUri.ts +28 -21
- package/src/domains/graphql/commands/build.test.ts +5 -3
- package/src/domains/graphql/commands/build.ts +8 -16
- package/src/domains/graphql/commands/check.test.ts +5 -3
- package/src/domains/graphql/commands/check.ts +7 -13
- package/src/domains/graphql/commands/index.ts +1 -0
- package/src/domains/graphql/commands/serve.test.ts +40 -0
- package/src/domains/graphql/commands/serve.ts +131 -0
- package/src/domains/graphql/constants.ts +10 -0
- package/src/domains/graphql/helpers/gatherSources.test.ts +92 -0
- package/src/domains/graphql/helpers/gatherSources.ts +58 -0
- package/src/domains/graphql/helpers/index.ts +2 -0
- package/src/domains/graphql/helpers/routeGraphql.test.ts +43 -0
- package/src/domains/graphql/helpers/routeGraphql.ts +28 -0
- package/src/domains/graphql/index.ts +9 -6
- package/src/domains/graphql/operations/compileSchema.test.ts +21 -45
- package/src/domains/graphql/operations/compileSchema.ts +23 -25
- package/src/domains/graphql/operations/createSchemaServer.test.ts +50 -0
- package/src/domains/graphql/operations/createSchemaServer.ts +75 -0
- package/src/domains/graphql/operations/index.ts +5 -0
- package/src/domains/info/operations/collectInfo.test.ts +14 -3
- package/src/domains/info/operations/collectInfo.ts +12 -15
- package/src/domains/llm/capabilities.test.ts +4 -4
- package/src/domains/llm/data/toolCatalog.ts +6 -0
- package/src/domains/modifier/commands/list.ts +5 -38
- package/src/domains/modifier/commands/lookup.ts +8 -79
- package/src/domains/modifier/mcp/specs.ts +15 -94
- package/src/domains/modifier/orchestration/resolveModifierLookup.ts +2 -1
- package/src/domains/modifier/stories.test.ts +20 -0
- package/src/domains/modifier/stories.ts +77 -0
- package/src/domains/ontology/commands/list.ts +5 -58
- package/src/domains/ontology/commands/show.ts +6 -58
- package/src/domains/ontology/formatters/show.ts +7 -1
- package/src/domains/ontology/mcp/specs.ts +7 -84
- package/src/domains/ontology/stories.test.ts +36 -0
- package/src/domains/ontology/stories.ts +108 -0
- package/src/domains/refs/operations/updateRefs.ts +2 -37
- package/src/domains/setup/commands/skills.test.ts +6 -2
- package/src/domains/setup/commands/skills.ts +6 -2
- package/src/domains/setup/helpers/runSetupTask.test.ts +148 -150
- package/src/domains/setup/helpers/runSetupTask.ts +62 -70
- package/src/domains/setup/operations/setupMcp.test.ts +6 -1
- package/src/domains/shared/bootStore.ts +6 -3
- package/src/domains/shared/compactUri.test.ts +11 -0
- package/src/domains/shared/compactUri.ts +10 -5
- package/src/domains/shared/context.ts +2 -2
- package/src/domains/shared/contracts.ts +11 -0
- package/src/domains/shared/createInteractivePromptSession.test.ts +439 -0
- package/src/domains/shared/createInteractivePromptSession.ts +236 -0
- package/src/domains/shared/loaders/bundledLoader.test.ts +248 -0
- package/src/domains/shared/loaders/bundledLoader.ts +110 -12
- package/src/domains/shared/loaders/gitLoader.ts +2 -2
- package/src/domains/shared/loaders/index.ts +4 -1
- package/src/domains/shared/loaders/localLoader.ts +18 -4
- package/src/domains/shared/loaders/readPackageDir.ts +36 -1
- package/src/domains/shared/loaders/storyAssetsPlugin.test.ts +84 -0
- package/src/domains/shared/loaders/storyAssetsPlugin.ts +46 -0
- package/src/domains/shared/lookupMany.test.ts +120 -0
- package/src/domains/shared/lookupMany.ts +34 -2
- package/src/domains/shared/mergeAndParseRefs.test.ts +89 -0
- package/src/domains/shared/mergeAndParseRefs.ts +44 -0
- package/src/domains/shared/packages.ts +10 -17
- package/src/domains/shared/resolveConfiguredGraphs.test.ts +27 -0
- package/src/domains/shared/resolveConfiguredGraphs.ts +41 -0
- package/src/domains/shared/runtime.ts +4 -63
- package/src/domains/shared/semanticPackage.ts +17 -1
- package/src/domains/shared/stories/compileLookupCommand.test.ts +145 -0
- package/src/domains/shared/stories/compileLookupCommand.ts +100 -0
- package/src/domains/shared/stories/compileLookupTool.test.ts +105 -0
- package/src/domains/shared/stories/compileLookupTool.ts +96 -0
- package/src/domains/shared/stories/compileReadCommand.test.ts +121 -0
- package/src/domains/shared/stories/compileReadCommand.ts +48 -0
- package/src/domains/shared/stories/compileReadTool.test.ts +64 -0
- package/src/domains/shared/stories/compileReadTool.ts +38 -0
- package/src/domains/shared/stories/condense.test.ts +18 -0
- package/src/domains/shared/stories/condense.ts +15 -0
- package/src/domains/shared/stories/index.ts +20 -0
- package/src/domains/shared/stories/normalizeNames.test.ts +20 -0
- package/src/domains/shared/stories/normalizeNames.ts +22 -0
- package/src/domains/shared/stories/pack/applyPackFilters.test.ts +102 -0
- package/src/domains/shared/stories/pack/applyPackFilters.ts +76 -0
- package/src/domains/shared/stories/pack/buildLookupQuery.test.ts +51 -0
- package/src/domains/shared/stories/pack/buildLookupQuery.ts +80 -0
- package/src/domains/shared/stories/pack/collectPackStories.test.ts +108 -0
- package/src/domains/shared/stories/pack/collectPackStories.ts +76 -0
- package/src/domains/shared/stories/pack/compilePackCommands.ts +40 -0
- package/src/domains/shared/stories/pack/compilePackStories.test.ts +146 -0
- package/src/domains/shared/stories/pack/compilePackStories.ts +253 -0
- package/src/domains/shared/stories/pack/compilePackToolSpecs.ts +43 -0
- package/src/domains/shared/stories/pack/index.ts +28 -0
- package/src/domains/shared/stories/pack/runSelectQuery.ts +31 -0
- package/src/domains/shared/stories/pack/standardParity.test.ts +308 -0
- package/src/domains/shared/stories/pack/types.ts +104 -0
- package/src/domains/shared/stories/pack/validateStoryPackDefinition.test.ts +240 -0
- package/src/domains/shared/stories/pack/validateStoryPackDefinition.ts +408 -0
- package/src/domains/shared/stories/projectCliParameters.test.ts +59 -0
- package/src/domains/shared/stories/projectCliParameters.ts +57 -0
- package/src/domains/shared/stories/projectToolParams.test.ts +50 -0
- package/src/domains/shared/stories/projectToolParams.ts +32 -0
- package/src/domains/shared/stories/requirePragmaContext.test.ts +23 -0
- package/src/domains/shared/stories/requirePragmaContext.ts +23 -0
- package/src/domains/shared/stories/resolveLookupDetailed.test.ts +57 -0
- package/src/domains/shared/stories/resolveLookupDetailed.ts +27 -0
- package/src/domains/shared/stories/types.ts +171 -0
- package/src/domains/shared/suggestions/expandGlob.ts +1 -0
- package/src/domains/shared/suggestions/suggestNames.ts +43 -10
- package/src/domains/skill/commands/index.ts +3 -1
- package/src/domains/skill/commands/list.test.ts +6 -2
- package/src/domains/skill/commands/list.ts +5 -63
- package/src/domains/skill/commands/lookup.test.ts +118 -0
- package/src/domains/skill/commands/lookup.ts +14 -0
- package/src/domains/skill/formatters/index.ts +1 -0
- package/src/domains/skill/formatters/lookup.test.ts +46 -0
- package/src/domains/skill/formatters/lookup.ts +57 -0
- package/src/domains/skill/helpers/resolveSkillSources.ts +0 -1
- package/src/domains/skill/index.ts +7 -3
- package/src/domains/skill/mcp/specs.ts +10 -29
- package/src/domains/skill/operations/discover.test.ts +61 -30
- package/src/domains/skill/operations/discover.ts +28 -37
- package/src/domains/skill/operations/index.ts +2 -1
- package/src/domains/skill/operations/list.test.ts +15 -7
- package/src/domains/skill/operations/lookup.test.ts +91 -0
- package/src/domains/skill/operations/lookup.ts +70 -0
- package/src/domains/skill/stories.test.ts +43 -0
- package/src/domains/skill/stories.ts +103 -0
- package/src/domains/skill/types.ts +8 -0
- package/src/domains/standard/commands/list.ts +5 -84
- package/src/domains/standard/commands/lookup.ts +8 -93
- package/src/domains/standard/mcp/specs.ts +16 -171
- package/src/domains/standard/orchestration/resolveStandardLookup.ts +2 -1
- package/src/domains/standard/stories.test.ts +24 -0
- package/src/domains/standard/stories.ts +151 -0
- package/src/domains/tier/commands/list.ts +5 -55
- package/src/domains/tier/mcp/specs.ts +5 -31
- package/src/domains/tier/stories.test.ts +22 -0
- package/src/domains/tier/stories.ts +52 -0
- package/src/domains/token/mcp/specs.ts +5 -1
- package/src/domains/token/orchestration/resolveTokenLookup.ts +2 -1
- package/src/domains/trace/tracePlugin.test.ts +1 -1
- package/src/mcp/registerResources.test.ts +382 -74
- package/src/mcp/registerResources.ts +5 -348
- package/src/mcp/resources/buildGraphIndex.ts +167 -0
- package/src/mcp/resources/buildResourceList.test.ts +97 -0
- package/src/mcp/resources/buildResourceList.ts +176 -0
- package/src/mcp/resources/classifyEntity.test.ts +40 -0
- package/src/mcp/resources/classifyEntity.ts +27 -0
- package/src/mcp/resources/constants.ts +105 -0
- package/src/mcp/resources/displayPredicates.ts +62 -0
- package/src/mcp/resources/pickFirstValue.test.ts +29 -0
- package/src/mcp/resources/pickFirstValue.ts +26 -0
- package/src/mcp/resources/pickPrimaryType.test.ts +41 -0
- package/src/mcp/resources/pickPrimaryType.ts +26 -0
- package/src/mcp/resources/rankUriCompletions.test.ts +52 -0
- package/src/mcp/resources/rankUriCompletions.ts +64 -0
- package/src/mcp/resources/readEntity.ts +169 -0
- package/src/mcp/resources/registerResources.ts +126 -0
- package/src/mcp/resources/types.ts +128 -0
- package/src/mcp/tools/index.ts +9 -0
- package/src/mcp/tools/registerTools.test.ts +65 -8
- package/src/mcp/types.ts +4 -0
- package/src/pipeline/collectCommands.ts +8 -1
- package/src/pipeline/runCli.ts +40 -6
- package/src/testing/createTestMcpClient.ts +4 -1
- package/src/testing/index.ts +10 -0
- package/src/testing/integration/__snapshots__/executor-golden.test.ts.snap +1207 -0
- package/src/testing/integration/cross-binary-byte-equality.test.ts +129 -0
- package/src/testing/integration/executor-execution.test.ts +101 -0
- package/src/testing/integration/executor-golden.test.ts +145 -0
- package/src/testing/integration/mcp-surface.test.ts +1 -1
- package/src/testing/integration/parity.test.ts +17 -12
- package/src/testing/regression/0001-skill-discovery-granularity.test.ts +71 -0
- package/src/testing/setupXdgIsolation.ts +15 -0
- package/src/testing/standardParityFixtures.ts +92 -0
- package/src/testing/storyFixtures.ts +63 -0
- package/src/pipeline/runInteractiveCommand.ts +0 -198
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { PragmaError } from "#error";
|
|
3
|
+
import resolveUri from "./resolveUri.js";
|
|
4
|
+
|
|
5
|
+
const prefixes = {
|
|
6
|
+
ds: "https://ds.canonical.com/",
|
|
7
|
+
cs: "http://pragma.canonical.com/codestandards#",
|
|
8
|
+
} as const;
|
|
9
|
+
|
|
10
|
+
describe("resolveUri", () => {
|
|
11
|
+
it("expands a registered prefix", () => {
|
|
12
|
+
expect(resolveUri("ds:UIBlock", prefixes)).toBe(
|
|
13
|
+
"https://ds.canonical.com/UIBlock",
|
|
14
|
+
);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("passes through a full http(s) IRI", () => {
|
|
18
|
+
expect(resolveUri("https://ds.canonical.com/global", prefixes)).toBe(
|
|
19
|
+
"https://ds.canonical.com/global",
|
|
20
|
+
);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("passes through a urn: IRI", () => {
|
|
24
|
+
expect(resolveUri("urn:isbn:0451450523", prefixes)).toBe(
|
|
25
|
+
"urn:isbn:0451450523",
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("passes through a foreign scheme:// IRI", () => {
|
|
30
|
+
expect(resolveUri("ftp://example.org/thing", prefixes)).toBe(
|
|
31
|
+
"ftp://example.org/thing",
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("throws for an unknown prefix", () => {
|
|
36
|
+
expect(() => resolveUri("unknown:something", prefixes)).toThrow(
|
|
37
|
+
PragmaError,
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("throws for a bare string without a prefix", () => {
|
|
42
|
+
expect(() => resolveUri("button", prefixes)).toThrow(PragmaError);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("throws for an IRI with unsafe characters", () => {
|
|
46
|
+
expect(() =>
|
|
47
|
+
resolveUri("https://example.com/<injected>", prefixes),
|
|
48
|
+
).toThrow(PragmaError);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -6,12 +6,20 @@ import { PragmaError } from "#error";
|
|
|
6
6
|
*/
|
|
7
7
|
const UNSAFE_IRI_PATTERN = /[<>"{}|\\^`\s]/;
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Matches a general absolute IRI: any scheme with an authority (`scheme://…`,
|
|
11
|
+
* covering http, https, ftp, ws, and foreign schemes) or a `urn:` opaque IRI.
|
|
12
|
+
* Broader than an http(s)-only check so foreign ontologies resolve.
|
|
13
|
+
*/
|
|
14
|
+
const ABSOLUTE_IRI_PATTERN = /^(?:[A-Za-z][A-Za-z0-9+.-]*:\/\/|urn:)\S/i;
|
|
15
|
+
|
|
9
16
|
/**
|
|
10
17
|
* Resolves a potentially prefixed URI (e.g. `ds:UIBlock`) to a full URI.
|
|
11
18
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
19
|
+
* A registered prefix always wins: `ds:UIBlock` expands via the prefix map.
|
|
20
|
+
* Otherwise a general absolute IRI (`scheme://…` or `urn:…`) is validated and
|
|
21
|
+
* returned as-is, so foreign ontologies are not rejected as unknown prefixes.
|
|
22
|
+
* Any other `token:rest` is treated as an unknown prefix.
|
|
15
23
|
*
|
|
16
24
|
* @param uri - A full or prefixed URI string.
|
|
17
25
|
* @param prefixes - Registered prefix-to-namespace mapping from the store.
|
|
@@ -22,11 +30,6 @@ export default function resolveUri(
|
|
|
22
30
|
uri: string,
|
|
23
31
|
prefixes: Readonly<Record<string, string>>,
|
|
24
32
|
): string {
|
|
25
|
-
if (uri.startsWith("http://") || uri.startsWith("https://")) {
|
|
26
|
-
assertSafeIri(uri);
|
|
27
|
-
return uri;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
33
|
const colonIdx = uri.indexOf(":");
|
|
31
34
|
if (colonIdx === -1) {
|
|
32
35
|
throw PragmaError.invalidInput("uri", uri, {
|
|
@@ -38,23 +41,27 @@ export default function resolveUri(
|
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
const prefix = uri.slice(0, colonIdx);
|
|
41
|
-
const localName = uri.slice(colonIdx + 1);
|
|
42
44
|
const namespace = prefixes[prefix];
|
|
43
45
|
|
|
44
|
-
if (namespace
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
if (namespace !== undefined) {
|
|
47
|
+
const resolved = `${namespace}${uri.slice(colonIdx + 1)}`;
|
|
48
|
+
assertSafeIri(resolved);
|
|
49
|
+
return resolved;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (ABSOLUTE_IRI_PATTERN.test(uri)) {
|
|
53
|
+
assertSafeIri(uri);
|
|
54
|
+
return uri;
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
throw PragmaError.invalidInput("prefix", prefix, {
|
|
58
|
+
validOptions: Object.keys(prefixes),
|
|
59
|
+
recovery: {
|
|
60
|
+
message: "List known ontology prefixes.",
|
|
61
|
+
cli: "pragma ontology list",
|
|
62
|
+
mcp: { tool: "ontology_list" },
|
|
63
|
+
},
|
|
64
|
+
});
|
|
58
65
|
}
|
|
59
66
|
|
|
60
67
|
/**
|
|
@@ -181,13 +181,15 @@ describe("graphql build command", () => {
|
|
|
181
181
|
expect(printed).toContain("C003");
|
|
182
182
|
});
|
|
183
183
|
|
|
184
|
-
it("throws structured error when sources
|
|
184
|
+
it("throws a structured error when explicit sources match nothing", async () => {
|
|
185
185
|
const ctx = makeCtx();
|
|
186
186
|
|
|
187
|
-
await expect(
|
|
187
|
+
await expect(
|
|
188
|
+
buildCommand.execute({ sources: ["no-such-*.ttl"] }, ctx),
|
|
189
|
+
).rejects.toMatchObject({
|
|
188
190
|
code: "INVALID_INPUT",
|
|
189
191
|
recovery: {
|
|
190
|
-
message: "
|
|
192
|
+
message: "No files matched the given paths or glob patterns.",
|
|
191
193
|
},
|
|
192
194
|
});
|
|
193
195
|
});
|
|
@@ -7,16 +7,13 @@ import {
|
|
|
7
7
|
createOutputResult,
|
|
8
8
|
} from "@canonical/cli-core";
|
|
9
9
|
import { serializeExtraction } from "@canonical/ke-graphql";
|
|
10
|
-
import { PragmaError } from "#error";
|
|
11
10
|
import { selectFormatter } from "../../shared/formatters.js";
|
|
11
|
+
import { DEFAULT_EXTRACTION_PATH, DEFAULT_SDL_PATH } from "../constants.js";
|
|
12
12
|
import { reportFormatters } from "../formatters/index.js";
|
|
13
|
-
import { parsePrefixes } from "../helpers/index.js";
|
|
13
|
+
import { gatherSources, parsePrefixes } from "../helpers/index.js";
|
|
14
14
|
import { compileSchema } from "../operations/index.js";
|
|
15
15
|
import type { GraphqlCompileReport } from "../types.js";
|
|
16
16
|
|
|
17
|
-
const DEFAULT_SDL_PATH = "./schema.graphql";
|
|
18
|
-
const DEFAULT_EXTRACTION_PATH = "./extraction.json";
|
|
19
|
-
|
|
20
17
|
/** Narrow an unknown param to a string array. */
|
|
21
18
|
function readStringArray(value: unknown): string[] {
|
|
22
19
|
return Array.isArray(value)
|
|
@@ -37,10 +34,10 @@ const buildCommand: CommandDefinition = {
|
|
|
37
34
|
parameters: [
|
|
38
35
|
{
|
|
39
36
|
name: "sources",
|
|
40
|
-
description:
|
|
37
|
+
description:
|
|
38
|
+
"TTL files or globs (default: the configured semantic packages)",
|
|
41
39
|
type: "multiselect",
|
|
42
40
|
positional: true,
|
|
43
|
-
required: true,
|
|
44
41
|
},
|
|
45
42
|
{
|
|
46
43
|
name: "sdl",
|
|
@@ -68,15 +65,10 @@ const buildCommand: CommandDefinition = {
|
|
|
68
65
|
],
|
|
69
66
|
},
|
|
70
67
|
async execute(params, ctx): Promise<CommandResult> {
|
|
71
|
-
const sources =
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
message: "Provide at least one TTL file or glob pattern.",
|
|
76
|
-
},
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
68
|
+
const sources = await gatherSources(
|
|
69
|
+
readStringArray(params.sources),
|
|
70
|
+
ctx.cwd,
|
|
71
|
+
);
|
|
80
72
|
const prefixes = parsePrefixes(readStringArray(params.prefix));
|
|
81
73
|
const outcome = await compileSchema({ sources, prefixes, cwd: ctx.cwd });
|
|
82
74
|
const format = selectFormatter(ctx, reportFormatters);
|
|
@@ -126,13 +126,15 @@ describe("graphql check command", () => {
|
|
|
126
126
|
expect(parsed.artifacts).toBeUndefined();
|
|
127
127
|
});
|
|
128
128
|
|
|
129
|
-
it("throws structured error when sources
|
|
129
|
+
it("throws a structured error when explicit sources match nothing", async () => {
|
|
130
130
|
const ctx = makeCtx();
|
|
131
131
|
|
|
132
|
-
await expect(
|
|
132
|
+
await expect(
|
|
133
|
+
checkCommand.execute({ sources: ["no-such-*.ttl"] }, ctx),
|
|
134
|
+
).rejects.toMatchObject({
|
|
133
135
|
code: "INVALID_INPUT",
|
|
134
136
|
recovery: {
|
|
135
|
-
message: "
|
|
137
|
+
message: "No files matched the given paths or glob patterns.",
|
|
136
138
|
},
|
|
137
139
|
});
|
|
138
140
|
});
|
|
@@ -4,10 +4,9 @@ import {
|
|
|
4
4
|
createExitResult,
|
|
5
5
|
createOutputResult,
|
|
6
6
|
} from "@canonical/cli-core";
|
|
7
|
-
import { PragmaError } from "#error";
|
|
8
7
|
import { selectFormatter } from "../../shared/formatters.js";
|
|
9
8
|
import { reportFormatters } from "../formatters/index.js";
|
|
10
|
-
import { parsePrefixes } from "../helpers/index.js";
|
|
9
|
+
import { gatherSources, parsePrefixes } from "../helpers/index.js";
|
|
11
10
|
import { compileSchema } from "../operations/index.js";
|
|
12
11
|
import type { GraphqlCompileReport } from "../types.js";
|
|
13
12
|
|
|
@@ -31,10 +30,10 @@ const checkCommand: CommandDefinition = {
|
|
|
31
30
|
parameters: [
|
|
32
31
|
{
|
|
33
32
|
name: "sources",
|
|
34
|
-
description:
|
|
33
|
+
description:
|
|
34
|
+
"TTL files or globs (default: the configured semantic packages)",
|
|
35
35
|
type: "multiselect",
|
|
36
36
|
positional: true,
|
|
37
|
-
required: true,
|
|
38
37
|
},
|
|
39
38
|
{
|
|
40
39
|
name: "prefix",
|
|
@@ -49,15 +48,10 @@ const checkCommand: CommandDefinition = {
|
|
|
49
48
|
],
|
|
50
49
|
},
|
|
51
50
|
async execute(params, ctx): Promise<CommandResult> {
|
|
52
|
-
const sources =
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
message: "Provide at least one TTL file or glob pattern.",
|
|
57
|
-
},
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
51
|
+
const sources = await gatherSources(
|
|
52
|
+
readStringArray(params.sources),
|
|
53
|
+
ctx.cwd,
|
|
54
|
+
);
|
|
61
55
|
const prefixes = parsePrefixes(readStringArray(params.prefix));
|
|
62
56
|
const outcome = await compileSchema({ sources, prefixes, cwd: ctx.cwd });
|
|
63
57
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
2
|
+
import type { PragmaContext } from "../../shared/context.js";
|
|
3
|
+
import serveCommand, { readPort } from "./serve.js";
|
|
4
|
+
|
|
5
|
+
describe("readPort", () => {
|
|
6
|
+
it("parses a valid port", () => {
|
|
7
|
+
expect(readPort("4001")).toBe(4001);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("falls back to the default for non-string values", () => {
|
|
11
|
+
expect(readPort(undefined)).toBe(4000);
|
|
12
|
+
expect(readPort(4001)).toBe(4000);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("falls back to the default for out-of-range or unparseable values", () => {
|
|
16
|
+
expect(readPort("0")).toBe(4000);
|
|
17
|
+
expect(readPort("70000")).toBe(4000);
|
|
18
|
+
expect(readPort("not-a-port")).toBe(4000);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("graphql serve command", () => {
|
|
23
|
+
const ctx = { cwd: process.cwd() } as PragmaContext;
|
|
24
|
+
const original = (globalThis as Record<string, unknown>).Bun;
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
(globalThis as Record<string, unknown>).Bun = original;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("requires the Bun runtime", async () => {
|
|
31
|
+
(globalThis as Record<string, unknown>).Bun = undefined;
|
|
32
|
+
|
|
33
|
+
await expect(serveCommand.execute({}, ctx)).rejects.toMatchObject({
|
|
34
|
+
code: "STORE_ERROR",
|
|
35
|
+
recovery: {
|
|
36
|
+
message: "Run the compiled `pragma` binary or use `bun run`.",
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type CommandDefinition,
|
|
3
|
+
type CommandResult,
|
|
4
|
+
createOutputResult,
|
|
5
|
+
} from "@canonical/cli-core";
|
|
6
|
+
import { PragmaError } from "#error";
|
|
7
|
+
import { DEFAULT_PORT } from "../constants.js";
|
|
8
|
+
import { gatherSources, parsePrefixes } from "../helpers/index.js";
|
|
9
|
+
import routeGraphql from "../helpers/routeGraphql.js";
|
|
10
|
+
import createSchemaServer, {
|
|
11
|
+
type SchemaServer,
|
|
12
|
+
} from "../operations/createSchemaServer.js";
|
|
13
|
+
|
|
14
|
+
/** Narrow an unknown param to a string array. */
|
|
15
|
+
function readStringArray(value: unknown): string[] {
|
|
16
|
+
return Array.isArray(value)
|
|
17
|
+
? value.filter((item): item is string => typeof item === "string")
|
|
18
|
+
: [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Parse the --port flag, falling back to the default on anything invalid. */
|
|
22
|
+
export function readPort(value: unknown): number {
|
|
23
|
+
if (typeof value !== "string") {
|
|
24
|
+
return DEFAULT_PORT;
|
|
25
|
+
}
|
|
26
|
+
const port = Number.parseInt(value, 10);
|
|
27
|
+
return Number.isInteger(port) && port > 0 && port < 65536
|
|
28
|
+
? port
|
|
29
|
+
: DEFAULT_PORT;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Listen on `port` and block until the process is interrupted, then stop
|
|
34
|
+
* the server and dispose the store.
|
|
35
|
+
*
|
|
36
|
+
* @note Impure — opens a network listener and registers signal handlers.
|
|
37
|
+
*/
|
|
38
|
+
/* v8 ignore start -- the live Bun.serve listener and signal loop run under `pragma graphql serve`, not unit tests */
|
|
39
|
+
async function listen(server: SchemaServer, port: number): Promise<void> {
|
|
40
|
+
const httpServer = Bun.serve({
|
|
41
|
+
port,
|
|
42
|
+
fetch: (request) => routeGraphql(request, server.handler, port),
|
|
43
|
+
});
|
|
44
|
+
await new Promise<void>((resolve) => {
|
|
45
|
+
const shutdown = (): void => {
|
|
46
|
+
httpServer.stop();
|
|
47
|
+
server.dispose();
|
|
48
|
+
resolve();
|
|
49
|
+
};
|
|
50
|
+
process.once("SIGINT", shutdown);
|
|
51
|
+
process.once("SIGTERM", shutdown);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/* v8 ignore stop */
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Builds the `pragma graphql serve` command definition.
|
|
58
|
+
*
|
|
59
|
+
* Compiles the configured ontologies (or explicit TTL sources) and serves
|
|
60
|
+
* a local GraphQL + GraphiQL endpoint until interrupted. With no positional
|
|
61
|
+
* sources it serves every TTL across the semantic packages in
|
|
62
|
+
* `pragma.config.json`.
|
|
63
|
+
*/
|
|
64
|
+
const serveCommand: CommandDefinition = {
|
|
65
|
+
path: ["graphql", "serve"],
|
|
66
|
+
description:
|
|
67
|
+
"Serve a local GraphQL + GraphiQL endpoint over the configured ontologies",
|
|
68
|
+
parameters: [
|
|
69
|
+
{
|
|
70
|
+
name: "sources",
|
|
71
|
+
description:
|
|
72
|
+
"TTL files or globs to serve (default: the configured semantic packages)",
|
|
73
|
+
type: "multiselect",
|
|
74
|
+
positional: true,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "port",
|
|
78
|
+
description: `Port to listen on (default ${DEFAULT_PORT})`,
|
|
79
|
+
type: "string",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "prefix",
|
|
83
|
+
description: "Ontology prefix as name=namespace (repeatable)",
|
|
84
|
+
type: "multiselect",
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
meta: {
|
|
88
|
+
examples: [
|
|
89
|
+
"pragma graphql serve",
|
|
90
|
+
"pragma graphql serve ontology.ttl --port 4001",
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
async execute(params, ctx): Promise<CommandResult> {
|
|
94
|
+
if (typeof Bun === "undefined") {
|
|
95
|
+
throw PragmaError.storeError(
|
|
96
|
+
"`pragma graphql serve` requires the Bun runtime.",
|
|
97
|
+
{
|
|
98
|
+
recovery: {
|
|
99
|
+
message: "Run the compiled `pragma` binary or use `bun run`.",
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const sources = await gatherSources(
|
|
106
|
+
readStringArray(params.sources),
|
|
107
|
+
ctx.cwd,
|
|
108
|
+
);
|
|
109
|
+
const prefixes = parsePrefixes(readStringArray(params.prefix));
|
|
110
|
+
const port = readPort(params.port);
|
|
111
|
+
|
|
112
|
+
const server = await createSchemaServer({ sources, prefixes });
|
|
113
|
+
const errorCount = server.diagnostics.filter(
|
|
114
|
+
(d) => d.severity === "error",
|
|
115
|
+
).length;
|
|
116
|
+
process.stdout.write(
|
|
117
|
+
`[ke-graphql] schema compiled — ${server.diagnostics.length} diagnostic(s)${
|
|
118
|
+
errorCount > 0 ? `, ${errorCount} error(s)` : ""
|
|
119
|
+
}\n[ke-graphql] GraphiQL on http://localhost:${port}/graphql (Ctrl-C to stop)\n`,
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
await listen(server, port);
|
|
123
|
+
|
|
124
|
+
return createOutputResult(
|
|
125
|
+
{ port, diagnostics: server.diagnostics },
|
|
126
|
+
{ plain: () => `Stopped the graphql server on port ${port}.` },
|
|
127
|
+
);
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export default serveCommand;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Default port `pragma graphql serve` listens on. */
|
|
2
|
+
const DEFAULT_PORT = 4000;
|
|
3
|
+
|
|
4
|
+
/** Default output path for the SDL written by `pragma graphql build`. */
|
|
5
|
+
const DEFAULT_SDL_PATH = "./schema.graphql";
|
|
6
|
+
|
|
7
|
+
/** Default output path for the extraction artifact written by `pragma graphql build`. */
|
|
8
|
+
const DEFAULT_EXTRACTION_PATH = "./extraction.json";
|
|
9
|
+
|
|
10
|
+
export { DEFAULT_EXTRACTION_PATH, DEFAULT_PORT, DEFAULT_SDL_PATH };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { GRAPHQL_CLEAN_TTL } from "#testing";
|
|
6
|
+
import resolveConfiguredGraphs from "../../shared/resolveConfiguredGraphs.js";
|
|
7
|
+
import gatherSources from "./gatherSources.js";
|
|
8
|
+
|
|
9
|
+
vi.mock("../../shared/resolveConfiguredGraphs.js", () => ({
|
|
10
|
+
default: vi.fn(),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
const mockResolve = vi.mocked(resolveConfiguredGraphs);
|
|
14
|
+
|
|
15
|
+
describe("gatherSources", () => {
|
|
16
|
+
let dir: string;
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
dir = mkdtempSync(join(tmpdir(), "pragma-gather-"));
|
|
20
|
+
writeFileSync(join(dir, "a.ttl"), GRAPHQL_CLEAN_TTL);
|
|
21
|
+
writeFileSync(join(dir, "b.ttl"), GRAPHQL_CLEAN_TTL);
|
|
22
|
+
mockResolve.mockReset();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
rmSync(dir, { recursive: true, force: true });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("reads explicit files into inline sources, ignoring the config", async () => {
|
|
30
|
+
const sources = await gatherSources(["a.ttl"], dir);
|
|
31
|
+
|
|
32
|
+
expect(sources).toEqual([
|
|
33
|
+
{
|
|
34
|
+
content: GRAPHQL_CLEAN_TTL,
|
|
35
|
+
format: "turtle",
|
|
36
|
+
path: join(dir, "a.ttl"),
|
|
37
|
+
},
|
|
38
|
+
]);
|
|
39
|
+
expect(mockResolve).not.toHaveBeenCalled();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("expands glob patterns", async () => {
|
|
43
|
+
const sources = await gatherSources(["*.ttl"], dir);
|
|
44
|
+
|
|
45
|
+
expect(sources.map((source) => source.path).sort()).toEqual([
|
|
46
|
+
join(dir, "a.ttl"),
|
|
47
|
+
join(dir, "b.ttl"),
|
|
48
|
+
]);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("throws when explicit sources match nothing", async () => {
|
|
52
|
+
await expect(gatherSources(["none-*.ttl"], dir)).rejects.toMatchObject({
|
|
53
|
+
code: "INVALID_INPUT",
|
|
54
|
+
recovery: {
|
|
55
|
+
message: "No files matched the given paths or glob patterns.",
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("falls back to the configured package graphs when no sources are given", async () => {
|
|
61
|
+
mockResolve.mockResolvedValue([
|
|
62
|
+
{
|
|
63
|
+
path: "definitions/ontology.ttl",
|
|
64
|
+
content: GRAPHQL_CLEAN_TTL,
|
|
65
|
+
format: "turtle",
|
|
66
|
+
},
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
const sources = await gatherSources([], dir);
|
|
70
|
+
|
|
71
|
+
expect(mockResolve).toHaveBeenCalledWith(dir);
|
|
72
|
+
expect(sources).toEqual([
|
|
73
|
+
{
|
|
74
|
+
content: GRAPHQL_CLEAN_TTL,
|
|
75
|
+
format: "turtle",
|
|
76
|
+
path: "definitions/ontology.ttl",
|
|
77
|
+
},
|
|
78
|
+
]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("throws when no sources are given and no packages resolve", async () => {
|
|
82
|
+
mockResolve.mockResolvedValue([]);
|
|
83
|
+
|
|
84
|
+
await expect(gatherSources([], dir)).rejects.toMatchObject({
|
|
85
|
+
code: "INVALID_INPUT",
|
|
86
|
+
recovery: {
|
|
87
|
+
message:
|
|
88
|
+
"No TTL sources given and no semantic packages resolved. Pass TTL files or globs, or configure `packages` in pragma.config.json and install them.",
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the TTL sources for a graphql command.
|
|
3
|
+
*
|
|
4
|
+
* When positional paths or globs are given they win; otherwise the sources
|
|
5
|
+
* are the union of every configured semantic package's graphs (the
|
|
6
|
+
* `pragma.config.json` packages, falling back to the defaults). Either way
|
|
7
|
+
* the result is a list of ke `InlineSource`s (content + provenance path),
|
|
8
|
+
* so the compiler and the dev server load the exact same bytes.
|
|
9
|
+
*
|
|
10
|
+
* @note Impure — reads files and resolves packages from disk.
|
|
11
|
+
*
|
|
12
|
+
* @param positional - Explicit TTL file paths or glob patterns (may be empty).
|
|
13
|
+
* @param cwd - Working directory for file and config resolution.
|
|
14
|
+
* @returns In-memory TTL sources to compile or serve.
|
|
15
|
+
* @throws PragmaError (INVALID_INPUT) when nothing resolves.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { readFileSync } from "node:fs";
|
|
19
|
+
import type { InlineSource } from "@canonical/ke";
|
|
20
|
+
import { PragmaError } from "#error";
|
|
21
|
+
import resolveConfiguredGraphs from "../../shared/resolveConfiguredGraphs.js";
|
|
22
|
+
import resolveSourceFiles from "./resolveSourceFiles.js";
|
|
23
|
+
|
|
24
|
+
export default async function gatherSources(
|
|
25
|
+
positional: readonly string[],
|
|
26
|
+
cwd: string,
|
|
27
|
+
): Promise<InlineSource[]> {
|
|
28
|
+
if (positional.length > 0) {
|
|
29
|
+
const files = resolveSourceFiles([...positional], cwd);
|
|
30
|
+
if (files.length === 0) {
|
|
31
|
+
throw PragmaError.invalidInput("sources", positional.join(", "), {
|
|
32
|
+
recovery: {
|
|
33
|
+
message: "No files matched the given paths or glob patterns.",
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return files.map((path) => ({
|
|
38
|
+
content: readFileSync(path, "utf-8"),
|
|
39
|
+
format: "turtle" as const,
|
|
40
|
+
path,
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const graphs = await resolveConfiguredGraphs(cwd);
|
|
45
|
+
if (graphs.length === 0) {
|
|
46
|
+
throw PragmaError.invalidInput("sources", "(none)", {
|
|
47
|
+
recovery: {
|
|
48
|
+
message:
|
|
49
|
+
"No TTL sources given and no semantic packages resolved. Pass TTL files or globs, or configure `packages` in pragma.config.json and install them.",
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return graphs.map((graph) => ({
|
|
54
|
+
content: graph.content,
|
|
55
|
+
format: graph.format,
|
|
56
|
+
path: graph.path,
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
/** @module Barrel for graphql helpers. */
|
|
2
2
|
|
|
3
|
+
export { default as gatherSources } from "./gatherSources.js";
|
|
3
4
|
export { default as parsePrefixes } from "./parsePrefixes.js";
|
|
4
5
|
export { default as resolveSourceFiles } from "./resolveSourceFiles.js";
|
|
6
|
+
export { default as routeGraphql } from "./routeGraphql.js";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import routeGraphql from "./routeGraphql.js";
|
|
3
|
+
|
|
4
|
+
describe("routeGraphql", () => {
|
|
5
|
+
it("delegates /graphql to the handler", async () => {
|
|
6
|
+
const handler = vi.fn(() => new Response("handled"));
|
|
7
|
+
const request = new Request("http://localhost:4000/graphql");
|
|
8
|
+
|
|
9
|
+
const response = await routeGraphql(request, handler, 4000);
|
|
10
|
+
|
|
11
|
+
expect(handler).toHaveBeenCalledWith(request);
|
|
12
|
+
expect(await response.text()).toBe("handled");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("redirects / to /graphql", async () => {
|
|
16
|
+
const handler = vi.fn();
|
|
17
|
+
|
|
18
|
+
const response = await routeGraphql(
|
|
19
|
+
new Request("http://localhost:4000/"),
|
|
20
|
+
handler,
|
|
21
|
+
4000,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
expect(response.status).toBe(302);
|
|
25
|
+
expect(response.headers.get("location")).toBe(
|
|
26
|
+
"http://localhost:4000/graphql",
|
|
27
|
+
);
|
|
28
|
+
expect(handler).not.toHaveBeenCalled();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("404s any other path", async () => {
|
|
32
|
+
const handler = vi.fn();
|
|
33
|
+
|
|
34
|
+
const response = await routeGraphql(
|
|
35
|
+
new Request("http://localhost:4000/favicon.ico"),
|
|
36
|
+
handler,
|
|
37
|
+
4000,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
expect(response.status).toBe(404);
|
|
41
|
+
expect(handler).not.toHaveBeenCalled();
|
|
42
|
+
});
|
|
43
|
+
});
|