@commercetools/cli-topic-integration-layer 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +1 -0
  2. package/dist/commands/integration-layer/explore.d.ts +20 -0
  3. package/dist/commands/integration-layer/explore.js +209 -0
  4. package/dist/commands/integration-layer/explore.js.map +1 -0
  5. package/dist/commands/integration-layer/extension/build.d.ts +2 -0
  6. package/dist/commands/integration-layer/extension/build.js +24 -3
  7. package/dist/commands/integration-layer/extension/build.js.map +1 -1
  8. package/dist/commands/integration-layer/extension/invoke.d.ts +1 -0
  9. package/dist/commands/integration-layer/extension/invoke.js +9 -4
  10. package/dist/commands/integration-layer/extension/invoke.js.map +1 -1
  11. package/dist/commands/integration-layer/extension/push.d.ts +15 -0
  12. package/dist/commands/integration-layer/extension/push.js +137 -3
  13. package/dist/commands/integration-layer/extension/push.js.map +1 -1
  14. package/dist/commands/integration-layer/extension/serve.d.ts +30 -0
  15. package/dist/commands/integration-layer/extension/serve.js +244 -6
  16. package/dist/commands/integration-layer/extension/serve.js.map +1 -1
  17. package/dist/commands/integration-layer/extension/status.js +4 -0
  18. package/dist/commands/integration-layer/extension/status.js.map +1 -1
  19. package/dist/commands/integration-layer/extension/validate.d.ts +2 -0
  20. package/dist/commands/integration-layer/extension/validate.js +24 -2
  21. package/dist/commands/integration-layer/extension/validate.js.map +1 -1
  22. package/dist/commands/integration-layer/init.js +228 -77
  23. package/dist/commands/integration-layer/init.js.map +1 -1
  24. package/dist/lib/awaitBundleState.d.ts +33 -0
  25. package/dist/lib/awaitBundleState.js +77 -0
  26. package/dist/lib/awaitBundleState.js.map +1 -0
  27. package/dist/lib/base.d.ts +35 -0
  28. package/dist/lib/base.js +99 -13
  29. package/dist/lib/base.js.map +1 -1
  30. package/dist/lib/ilClient.d.ts +62 -2
  31. package/dist/lib/ilClient.js +42 -2
  32. package/dist/lib/ilClient.js.map +1 -1
  33. package/dist/lib/sourceRevision.d.ts +39 -0
  34. package/dist/lib/sourceRevision.js +95 -0
  35. package/dist/lib/sourceRevision.js.map +1 -0
  36. package/dist/lib/tooling/exploreSchema.d.ts +15 -0
  37. package/dist/lib/tooling/exploreSchema.js +77 -0
  38. package/dist/lib/tooling/exploreSchema.js.map +1 -0
  39. package/dist/lib/tooling/explorerPage.d.ts +1 -0
  40. package/dist/lib/tooling/explorerPage.js +96 -0
  41. package/dist/lib/tooling/explorerPage.js.map +1 -0
  42. package/dist/lib/tooling/explorerServer.d.ts +27 -0
  43. package/dist/lib/tooling/explorerServer.js +134 -0
  44. package/dist/lib/tooling/explorerServer.js.map +1 -0
  45. package/dist/lib/tooling/extensions.d.ts +77 -0
  46. package/dist/lib/tooling/extensions.js +209 -0
  47. package/dist/lib/tooling/extensions.js.map +1 -0
  48. package/dist/lib/tooling/introspectionRequest.d.ts +13 -0
  49. package/dist/lib/tooling/introspectionRequest.js +47 -0
  50. package/dist/lib/tooling/introspectionRequest.js.map +1 -0
  51. package/dist/lib/tooling/session.d.ts +36 -0
  52. package/dist/lib/tooling/session.js +99 -0
  53. package/dist/lib/tooling/session.js.map +1 -0
  54. package/package.json +10 -12
@@ -0,0 +1,96 @@
1
+ // The GraphiQL page the local explorer serves at `/`.
2
+ //
3
+ // A static HTML shell loading GraphiQL from a CDN (esm.sh, version-pinned + SRI),
4
+ // pointed at this same local server's `/graphql`. Nothing is templated into it: the
5
+ // project key, the edge URL, and the session bearer all live server-side, so the
6
+ // page holds no credential and needs no configuration.
7
+ //
8
+ // The local server answers introspection itself from the resolved schema and proxies
9
+ // everything else to the deployed edge, so GraphiQL gets full docs/autocomplete even
10
+ // though the edge itself has introspection disabled.
11
+ export const EXPLORER_HTML = String.raw `<!doctype html>
12
+ <html lang="en">
13
+ <head>
14
+ <meta charset="utf-8" />
15
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
16
+ <title>GraphQL Explorer · commercetools integration layer</title>
17
+ <style>
18
+ body { margin: 0; height: 100dvh; overflow: hidden; }
19
+ #graphiql { height: 100dvh; }
20
+ .loading { display: flex; height: 100dvh; align-items: center;
21
+ justify-content: center; font-family: ui-sans-serif, system-ui, sans-serif;
22
+ color: #6b7280; }
23
+ </style>
24
+ <link
25
+ rel="stylesheet"
26
+ href="https://esm.sh/graphiql@5.2.4/dist/style.css"
27
+ integrity="sha384-TFpQQKp325U5sd3PddH4cS0KOB3Gz/aqdEe12Mqkkq3wm2MGcDhRX5WhWf+o8akh"
28
+ crossorigin="anonymous"
29
+ />
30
+ <link
31
+ rel="stylesheet"
32
+ href="https://esm.sh/@graphiql/plugin-explorer@5.1.3/dist/style.css"
33
+ integrity="sha384-vTFGj0krVqwFXLB7kq/VHR0/j2+cCT/B63rge2mULaqnib2OX7DVLUVksTlqvMab"
34
+ crossorigin="anonymous"
35
+ />
36
+ <script type="importmap">
37
+ {
38
+ "imports": {
39
+ "react": "https://esm.sh/react@19.2.0",
40
+ "react/jsx-runtime": "https://esm.sh/react@19.2.0/jsx-runtime",
41
+ "react-dom": "https://esm.sh/react-dom@19.2.0",
42
+ "react-dom/client": "https://esm.sh/react-dom@19.2.0/client",
43
+ "graphql": "https://esm.sh/graphql@16.11.0",
44
+ "graphiql": "https://esm.sh/graphiql@5.2.4?standalone&external=react,react-dom,@graphiql/react,graphql",
45
+ "graphiql/": "https://esm.sh/graphiql@5.2.4/",
46
+ "@graphiql/plugin-explorer": "https://esm.sh/@graphiql/plugin-explorer@5.1.3?standalone&external=react,@graphiql/react,graphql",
47
+ "@graphiql/react": "https://esm.sh/@graphiql/react@0.37.7?standalone&external=react,react-dom,graphql,@graphiql/toolkit,@emotion/is-prop-valid",
48
+ "@graphiql/toolkit": "https://esm.sh/@graphiql/toolkit@0.12.1?standalone&external=graphql"
49
+ }
50
+ }
51
+ </script>
52
+ </head>
53
+ <body>
54
+ <div id="graphiql"><div class="loading">Loading the explorer…</div></div>
55
+ <script type="module">
56
+ import React from 'react';
57
+ import ReactDOM from 'react-dom/client';
58
+ import { GraphiQL, HISTORY_PLUGIN } from 'graphiql';
59
+ import { createGraphiQLFetcher } from '@graphiql/toolkit';
60
+ import { explorerPlugin } from '@graphiql/plugin-explorer';
61
+ import 'graphiql/setup-workers/esm.sh';
62
+
63
+ // Same-origin: this page and the GraphQL endpoint are both served by the CLI's
64
+ // local server, which holds the session bearer and forwards to the edge.
65
+ const fetcher = createGraphiQLFetcher({ url: location.origin + '/graphql' });
66
+
67
+ const plugins = [HISTORY_PLUGIN, explorerPlugin()];
68
+
69
+ const DEFAULT_QUERY = [
70
+ '# Local GraphQL explorer for your commercetools integration layer.',
71
+ '#',
72
+ '# Operations run against the DEPLOYED edge under the session shown in your',
73
+ '# terminal. Docs and autocomplete come from the schema the CLI resolved.',
74
+ '',
75
+ 'query Categories {',
76
+ ' categories {',
77
+ ' items {',
78
+ ' name',
79
+ ' }',
80
+ ' }',
81
+ '}',
82
+ '',
83
+ ].join('\n');
84
+
85
+ ReactDOM.createRoot(document.getElementById('graphiql')).render(
86
+ React.createElement(GraphiQL, {
87
+ fetcher,
88
+ plugins,
89
+ defaultQuery: DEFAULT_QUERY,
90
+ }),
91
+ );
92
+ </script>
93
+ </body>
94
+ </html>
95
+ `;
96
+ //# sourceMappingURL=explorerPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explorerPage.js","sourceRoot":"","sources":["../../../src/lib/tooling/explorerPage.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,EAAE;AACF,kFAAkF;AAClF,oFAAoF;AACpF,iFAAiF;AACjF,uDAAuD;AACvD,EAAE;AACF,qFAAqF;AACrF,qFAAqF;AACrF,qDAAqD;AAErD,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoFtC,CAAC"}
@@ -0,0 +1,27 @@
1
+ import { type Server } from "node:http";
2
+ import { type GraphQLSchema } from "graphql";
3
+ /** The GraphQL-over-HTTP request body GraphiQL sends. */
4
+ export interface GraphQLBody {
5
+ query?: string;
6
+ variables?: Record<string, unknown>;
7
+ operationName?: string;
8
+ }
9
+ export interface ExplorerServerOptions {
10
+ /** The schema introspection is answered from (docs, autocomplete, validation). */
11
+ schema: GraphQLSchema;
12
+ /** Absolute URL of the deployed edge's per-project GraphQL endpoint. */
13
+ endpoint: string;
14
+ /** Session bearer attached to every proxied operation. Never sent to the browser. */
15
+ bearer: string;
16
+ }
17
+ /**
18
+ * Build (but do not listen on) the explorer's server.
19
+ *
20
+ * The edge endpoint is validated ONCE here, not per request: it must be an
21
+ * absolute `http:`/`https:` URL. It comes from the operator's own login region or
22
+ * their explicit `--graphql-url` / `IL_GRAPHQL_URL`, so this is not a trust
23
+ * boundary — but pinning the scheme up front means a typo or a stray `file://`
24
+ * fails at startup with a clear message rather than at the first query, and the
25
+ * value the proxy uses can never be anything else.
26
+ */
27
+ export declare function createExplorerServer(opts: ExplorerServerOptions): Server;
@@ -0,0 +1,134 @@
1
+ // The explorer's local HTTP server.
2
+ //
3
+ // Three responsibilities, and the split between the last two is the whole design:
4
+ //
5
+ // GET / the GraphiQL page
6
+ // POST /graphql introspection → answered HERE from the resolved schema, because
7
+ // the deployed edge runs `introspection: false`
8
+ // everything else → proxied to the edge under a session bearer
9
+ //
10
+ // The bearer never reaches the browser: the page is same-origin and this process
11
+ // attaches the credential on the way out.
12
+ //
13
+ // Kept out of the command so it can be started and driven in tests against a stub
14
+ // edge, rather than only exercised by hand.
15
+ import { createServer } from "node:http";
16
+ import { execute, parse, validate } from "graphql";
17
+ import { EXPLORER_HTML } from "./explorerPage.js";
18
+ import { isIntrospectionRequest } from "./introspectionRequest.js";
19
+ /**
20
+ * Build (but do not listen on) the explorer's server.
21
+ *
22
+ * The edge endpoint is validated ONCE here, not per request: it must be an
23
+ * absolute `http:`/`https:` URL. It comes from the operator's own login region or
24
+ * their explicit `--graphql-url` / `IL_GRAPHQL_URL`, so this is not a trust
25
+ * boundary — but pinning the scheme up front means a typo or a stray `file://`
26
+ * fails at startup with a clear message rather than at the first query, and the
27
+ * value the proxy uses can never be anything else.
28
+ */
29
+ export function createExplorerServer(opts) {
30
+ let parsed;
31
+ try {
32
+ parsed = new URL(opts.endpoint);
33
+ }
34
+ catch {
35
+ throw new Error(`the GraphQL edge endpoint is not a valid URL: ${opts.endpoint}`);
36
+ }
37
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
38
+ throw new Error(`the GraphQL edge endpoint must be http(s), got '${parsed.protocol}': ${opts.endpoint}`);
39
+ }
40
+ const endpoint = parsed.toString();
41
+ return createServer((req, res) => {
42
+ void handle(req, res, { ...opts, endpoint });
43
+ });
44
+ }
45
+ async function handle(req, res, opts) {
46
+ const path = (req.url ?? "/").split("?")[0];
47
+ const json = (status, payload) => {
48
+ res.writeHead(status, { "content-type": "application/json" });
49
+ res.end(JSON.stringify(payload));
50
+ };
51
+ if (req.method === "GET" && (path === "/" || path === "/index.html")) {
52
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
53
+ res.end(EXPLORER_HTML);
54
+ return;
55
+ }
56
+ if (path !== "/graphql") {
57
+ json(404, { errors: [{ message: `no route ${path}` }] });
58
+ return;
59
+ }
60
+ if (req.method !== "POST") {
61
+ json(405, { errors: [{ message: "the GraphQL endpoint accepts POST" }] });
62
+ return;
63
+ }
64
+ let body;
65
+ try {
66
+ body = JSON.parse(await readBody(req));
67
+ }
68
+ catch (err) {
69
+ json(400, { errors: [{ message: `invalid JSON body: ${err.message}` }] });
70
+ return;
71
+ }
72
+ const query = body.query ?? "";
73
+ if (!query.trim()) {
74
+ json(400, { errors: [{ message: "request body must include a 'query'" }] });
75
+ return;
76
+ }
77
+ if (isIntrospectionRequest(query, body.operationName)) {
78
+ let document;
79
+ try {
80
+ document = parse(query);
81
+ }
82
+ catch (err) {
83
+ json(400, { errors: [{ message: err.message }] });
84
+ return;
85
+ }
86
+ const errors = validate(opts.schema, document);
87
+ if (errors.length) {
88
+ json(400, { errors: errors.map((e) => ({ message: e.message })) });
89
+ return;
90
+ }
91
+ json(200, await execute({
92
+ schema: opts.schema,
93
+ document,
94
+ variableValues: body.variables,
95
+ operationName: body.operationName,
96
+ }));
97
+ return;
98
+ }
99
+ try {
100
+ // Not SSRF: `opts.endpoint` is fixed when the server is constructed, from the
101
+ // operator's OWN login region or their explicit --graphql-url/IL_GRAPHQL_URL,
102
+ // and validated to be an absolute http(s) URL there. Nothing in the request
103
+ // influences it — the body is forwarded, the destination is not. The server
104
+ // also binds 127.0.0.1, so the only caller is the developer who chose the URL;
105
+ // there is no privilege boundary for a forged request to cross.
106
+ // nosemgrep
107
+ const upstream = await fetch(opts.endpoint, {
108
+ method: "POST",
109
+ headers: {
110
+ "content-type": "application/json",
111
+ accept: "application/json",
112
+ authorization: `Bearer ${opts.bearer}`,
113
+ },
114
+ body: JSON.stringify(body),
115
+ });
116
+ const text = await upstream.text();
117
+ res.writeHead(upstream.status, {
118
+ "content-type": upstream.headers.get("content-type") ?? "application/json",
119
+ });
120
+ res.end(text);
121
+ }
122
+ catch (err) {
123
+ json(502, {
124
+ errors: [{ message: `could not reach ${opts.endpoint}: ${err.message}` }],
125
+ });
126
+ }
127
+ }
128
+ async function readBody(req) {
129
+ const chunks = [];
130
+ for await (const chunk of req)
131
+ chunks.push(chunk);
132
+ return Buffer.concat(chunks).toString("utf8");
133
+ }
134
+ //# sourceMappingURL=explorerServer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explorerServer.js","sourceRoot":"","sources":["../../../src/lib/tooling/explorerServer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ import { type GraphQLSchema } from "graphql";
2
+ /** An extension package discovered under the workspace's extensions/ directory. */
3
+ export interface DiscoveredExtension {
4
+ /** The package/directory name (used only for messages — the combined subgraph has one name). */
5
+ name: string;
6
+ /** The extension source entry (`<dir>/src/extension.ts`). */
7
+ entry: string;
8
+ /** The esbuild bundle output for a standalone build (`<dir>/dist/extension.js`). */
9
+ outfile: string;
10
+ }
11
+ /**
12
+ * Discover every extension package under `<root>/<dirName>` that has a
13
+ * `src/extension.ts`. Sorted by name for stable, deterministic merge output.
14
+ */
15
+ export declare function discoverExtensions(root: string, dirName: string): Promise<DiscoveredExtension[]>;
16
+ /** One extension's contribution to the combined subgraph. */
17
+ export interface ExtensionModule {
18
+ name: string;
19
+ typeDefs: string;
20
+ resolvers: object;
21
+ }
22
+ /** The single subgraph all extensions merge into. */
23
+ export interface MergedSubgraph {
24
+ /** Executable subgraph schema (typeDefs + wired resolvers) — for serving. */
25
+ schema: GraphQLSchema;
26
+ /** Federation SDL of the merged subgraph — for composing with the core / publishing. */
27
+ sdl: string;
28
+ }
29
+ /**
30
+ * Merge several extension modules into ONE federation subgraph — the single bundle a
31
+ * project deploys. Each extension's SDL is a self-contained subgraph (its own
32
+ * `type Query`, its own `@link` header), so we can't just concatenate them
33
+ * (`buildSubgraphSchema` would see two `type Query`). `mergeTypeDefs` folds same-named
34
+ * types into one — so two extensions can each contribute fields to `Query` — and dedupes
35
+ * the repeated `@link`.
36
+ *
37
+ * First we reject two extensions declaring the SAME field (even identically): merged into
38
+ * one bundle their resolvers would silently shadow each other, so it's an authoring error
39
+ * we name loudly rather than paper over. The merged SDL is printed with
40
+ * `printSubgraphSchema`, so it round-trips as the exact federation SDL the IL validates /
41
+ * publishes for the deployed bundle.
42
+ */
43
+ export declare function mergeExtensionSubgraph(modules: ExtensionModule[]): MergedSubgraph;
44
+ /** Result of a combined build — the same shape {@link buildBundle} returns. */
45
+ export interface CombinedBuildResult {
46
+ /** Absolute path to the single combined bundle. */
47
+ outfile: string;
48
+ /** The authors' source files that went into it (for static analysis; excludes the generated entry). */
49
+ sourceFiles: string[];
50
+ }
51
+ /**
52
+ * Build ONE combined bundle from every discovered extension — the artifact `push`
53
+ * uploads. Each extension is bundled + loaded to read its `typeDefs` (merged into one
54
+ * SDL, so a clash fails here on the author's machine) and to be re-imported by a
55
+ * generated entry that, at load time, merges the resolvers (per type, then per field),
56
+ * concatenates `apiExtensions`, and merges `hooks`. That entry is then bundled into the
57
+ * final self-contained artifact — a faithful stand-in for the single deployed bundle.
58
+ */
59
+ export declare function buildCombinedBundle(extensions: DiscoveredExtension[], outfile: string): Promise<CombinedBuildResult>;
60
+ /** What {@link bundleForFlags} produced, plus a label for the command's log line. */
61
+ export interface BundleForFlags extends CombinedBuildResult {
62
+ /** Human description of what was bundled (the entry, or "N extension(s) (…)"). */
63
+ describe: string;
64
+ }
65
+ /**
66
+ * The single build step shared by `build`/`validate`/`push`: bundle either the one
67
+ * extension in the cwd (default) or — with `--all` — every extension under
68
+ * `<extensionsDir>/*` merged into the one combined bundle a project deploys. Throws a
69
+ * clean message when `--all` finds nothing to build.
70
+ */
71
+ export declare function bundleForFlags(opts: {
72
+ all: boolean;
73
+ extensionsDir: string;
74
+ entry: string;
75
+ out: string;
76
+ cwd?: string;
77
+ }): Promise<BundleForFlags>;
@@ -0,0 +1,209 @@
1
+ // Monorepo helpers for the "one bundle per project" model. In production a project
2
+ // has a SINGLE extension bundle = one federation subgraph (all of a repo's extensions
3
+ // merged), which the router then composes with the integration-layer core subgraph.
4
+ // So a monorepo of `extensions/*` packages must:
5
+ // - MERGE into one subgraph for local `serve --all` (mergeExtensionSubgraph), and
6
+ // - BUILD into one bundle for `build/validate/push --all` (buildCombinedBundle),
7
+ // never one-subgraph-per-extension (that's not the deployed shape).
8
+ import { readdir, readFile, stat, writeFile, mkdtemp } from "node:fs/promises";
9
+ import { tmpdir } from "node:os";
10
+ import { join, resolve } from "node:path";
11
+ import process from "node:process";
12
+ import { buildSubgraphSchema, printSubgraphSchema } from "@apollo/subgraph";
13
+ import { mergeTypeDefs } from "@graphql-tools/merge";
14
+ import { Kind, parse } from "graphql";
15
+ import { buildBundle } from "./build.js";
16
+ import { loadBundleSource } from "./loadBundle.js";
17
+ /**
18
+ * Discover every extension package under `<root>/<dirName>` that has a
19
+ * `src/extension.ts`. Sorted by name for stable, deterministic merge output.
20
+ */
21
+ export async function discoverExtensions(root, dirName) {
22
+ const base = join(root, dirName);
23
+ const dirents = await readdir(base, { withFileTypes: true }).catch(() => []);
24
+ const found = [];
25
+ for (const dirent of dirents) {
26
+ if (!dirent.isDirectory())
27
+ continue;
28
+ const entry = join(base, dirent.name, "src", "extension.ts");
29
+ const hasEntry = await stat(entry)
30
+ .then((s) => s.isFile())
31
+ .catch(() => false);
32
+ if (!hasEntry)
33
+ continue;
34
+ found.push({
35
+ name: dirent.name,
36
+ entry,
37
+ outfile: join(base, dirent.name, "dist", "extension.js"),
38
+ });
39
+ }
40
+ return found.sort((a, b) => a.name.localeCompare(b.name));
41
+ }
42
+ /** Collect `Type.field` keys an SDL declares on object/interface types (incl. extensions). */
43
+ function declaredFields(sdl) {
44
+ const out = [];
45
+ for (const def of parse(sdl).definitions) {
46
+ if (def.kind === Kind.OBJECT_TYPE_DEFINITION ||
47
+ def.kind === Kind.OBJECT_TYPE_EXTENSION ||
48
+ def.kind === Kind.INTERFACE_TYPE_DEFINITION ||
49
+ def.kind === Kind.INTERFACE_TYPE_EXTENSION) {
50
+ for (const f of def.fields ?? [])
51
+ out.push({ type: def.name.value, field: f.name.value });
52
+ }
53
+ }
54
+ return out;
55
+ }
56
+ /**
57
+ * Merge several extension modules into ONE federation subgraph — the single bundle a
58
+ * project deploys. Each extension's SDL is a self-contained subgraph (its own
59
+ * `type Query`, its own `@link` header), so we can't just concatenate them
60
+ * (`buildSubgraphSchema` would see two `type Query`). `mergeTypeDefs` folds same-named
61
+ * types into one — so two extensions can each contribute fields to `Query` — and dedupes
62
+ * the repeated `@link`.
63
+ *
64
+ * First we reject two extensions declaring the SAME field (even identically): merged into
65
+ * one bundle their resolvers would silently shadow each other, so it's an authoring error
66
+ * we name loudly rather than paper over. The merged SDL is printed with
67
+ * `printSubgraphSchema`, so it round-trips as the exact federation SDL the IL validates /
68
+ * publishes for the deployed bundle.
69
+ */
70
+ export function mergeExtensionSubgraph(modules) {
71
+ if (modules.length === 0) {
72
+ throw new Error("no extension exports a GraphQL subgraph (typeDefs) to merge");
73
+ }
74
+ const resolvers = {};
75
+ const sdls = [];
76
+ const owner = new Map(); // "Type.field" -> first extension that declared it
77
+ for (const m of modules) {
78
+ if (typeof m.typeDefs !== "string" || m.typeDefs.trim() === "") {
79
+ throw new Error(`extension '${m.name}' must export a non-empty \`typeDefs\` string`);
80
+ }
81
+ if (m.resolvers === null || typeof m.resolvers !== "object") {
82
+ throw new Error(`extension '${m.name}' with \`typeDefs\` must also export a \`resolvers\` object`);
83
+ }
84
+ let fields;
85
+ try {
86
+ fields = declaredFields(m.typeDefs);
87
+ }
88
+ catch (err) {
89
+ throw new Error(`extension '${m.name}' has invalid \`typeDefs\`: ${err.message}`);
90
+ }
91
+ for (const { type, field } of fields) {
92
+ const key = `${type}.${field}`;
93
+ const prev = owner.get(key);
94
+ if (prev !== undefined && prev !== m.name) {
95
+ throw new Error(`extensions '${prev}' and '${m.name}' both declare \`${key}\` — a project ships one bundle, so each field must be owned by a single extension`);
96
+ }
97
+ owner.set(key, m.name);
98
+ }
99
+ sdls.push(m.typeDefs);
100
+ // Merge resolvers per type then per field — the same shape the combined bundle uses.
101
+ for (const [type, fieldResolvers] of Object.entries(m.resolvers)) {
102
+ resolvers[type] = Object.assign(resolvers[type] ?? {}, fieldResolvers);
103
+ }
104
+ }
105
+ let schema;
106
+ try {
107
+ // `mergeTypeDefs` folds the per-extension subgraphs into one document. `@apollo/subgraph`
108
+ // types `resolvers` as its internal `GraphQLResolverMap`; cast through the param type.
109
+ const typeDefs = mergeTypeDefs(sdls);
110
+ schema = buildSubgraphSchema({ typeDefs, resolvers });
111
+ }
112
+ catch (err) {
113
+ throw new Error(`extensions do not merge into a single subgraph: ${err.message}`);
114
+ }
115
+ return { schema, sdl: printSubgraphSchema(schema) };
116
+ }
117
+ /**
118
+ * Build ONE combined bundle from every discovered extension — the artifact `push`
119
+ * uploads. Each extension is bundled + loaded to read its `typeDefs` (merged into one
120
+ * SDL, so a clash fails here on the author's machine) and to be re-imported by a
121
+ * generated entry that, at load time, merges the resolvers (per type, then per field),
122
+ * concatenates `apiExtensions`, and merges `hooks`. That entry is then bundled into the
123
+ * final self-contained artifact — a faithful stand-in for the single deployed bundle.
124
+ */
125
+ export async function buildCombinedBundle(extensions, outfile) {
126
+ if (extensions.length === 0) {
127
+ throw new Error("no extensions to build");
128
+ }
129
+ // 1. Bundle + load each extension to read its exported subgraph (typeDefs/resolvers).
130
+ const tmp = await mkdtemp(join(tmpdir(), "il-combine-"));
131
+ const loaded = [];
132
+ for (const ext of extensions) {
133
+ const out = join(tmp, `${ext.name}.js`);
134
+ await buildBundle(ext.entry, out);
135
+ loaded.push({ name: ext.name, module: loadBundleSource(await readFile(out, "utf8")) });
136
+ }
137
+ // 2. Merge the subgraph-bearing extensions into one SDL (conflict detection). An
138
+ // extension may be API-extensions-only (no typeDefs) — those contribute no SDL.
139
+ const withSubgraph = loaded.filter((l) => typeof l.module.typeDefs === "string" && l.module.typeDefs.trim() !== "");
140
+ const mergedSdl = withSubgraph.length > 0
141
+ ? mergeExtensionSubgraph(withSubgraph.map((l) => ({
142
+ name: l.name,
143
+ typeDefs: l.module.typeDefs,
144
+ resolvers: l.module.resolvers ?? {},
145
+ }))).sdl
146
+ : undefined;
147
+ // 3. Generate the combining entry: re-import each extension, merge at load time.
148
+ const imports = extensions
149
+ .map((e, i) => `import * as e${i} from ${JSON.stringify(e.entry)};`)
150
+ .join("\n");
151
+ const modulesArray = `[${extensions.map((_, i) => `e${i}`).join(", ")}]`;
152
+ const entrySource = `${imports}
153
+
154
+ const __modules = ${modulesArray};
155
+
156
+ ${mergedSdl !== undefined ? `export const typeDefs = ${JSON.stringify(mergedSdl)};` : ""}
157
+
158
+ const __mergeResolvers = (mods) => {
159
+ const out = {};
160
+ for (const m of mods) {
161
+ const r = m && m.resolvers;
162
+ if (!r || typeof r !== "object") continue;
163
+ for (const type of Object.keys(r)) {
164
+ out[type] = Object.assign(out[type] ?? {}, r[type]);
165
+ }
166
+ }
167
+ return out;
168
+ };
169
+
170
+ export const resolvers = __mergeResolvers(__modules);
171
+ export const apiExtensions = __modules.flatMap((m) =>
172
+ m && Array.isArray(m.apiExtensions) ? m.apiExtensions : [],
173
+ );
174
+ export const hooks = Object.assign(
175
+ {},
176
+ ...__modules.map((m) => (m && m.hooks && typeof m.hooks === "object" ? m.hooks : {})),
177
+ );
178
+ `;
179
+ const entryPath = join(tmp, "combined-entry.ts");
180
+ await writeFile(entryPath, entrySource, "utf8");
181
+ // 4. Bundle the generated entry into the single artifact. Its imported author sources
182
+ // feed static analysis; the generated entry itself is trusted, so drop it.
183
+ const { sourceFiles } = await buildBundle(entryPath, outfile);
184
+ const generated = resolve(entryPath);
185
+ return { outfile, sourceFiles: sourceFiles.filter((f) => resolve(f) !== generated) };
186
+ }
187
+ /**
188
+ * The single build step shared by `build`/`validate`/`push`: bundle either the one
189
+ * extension in the cwd (default) or — with `--all` — every extension under
190
+ * `<extensionsDir>/*` merged into the one combined bundle a project deploys. Throws a
191
+ * clean message when `--all` finds nothing to build.
192
+ */
193
+ export async function bundleForFlags(opts) {
194
+ if (!opts.all) {
195
+ const { outfile, sourceFiles } = await buildBundle(opts.entry, opts.out);
196
+ return { outfile, sourceFiles, describe: opts.entry };
197
+ }
198
+ const extensions = await discoverExtensions(opts.cwd ?? process.cwd(), opts.extensionsDir);
199
+ if (extensions.length === 0) {
200
+ throw new Error(`no extensions found under ./${opts.extensionsDir}/*/src/extension.ts — run --all from the monorepo root`);
201
+ }
202
+ const { outfile, sourceFiles } = await buildCombinedBundle(extensions, opts.out);
203
+ return {
204
+ outfile,
205
+ sourceFiles,
206
+ describe: `${extensions.length} extension(s) (${extensions.map((e) => e.name).join(", ")})`,
207
+ };
208
+ }
209
+ //# sourceMappingURL=extensions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extensions.js","sourceRoot":"","sources":["../../../src/lib/tooling/extensions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * True when `query`'s selected operation is a query whose every top-level selection
3
+ * is an introspection meta-field (`__schema`, `__type`, `__typename`).
4
+ *
5
+ * Unparseable input is NOT introspection: forwarding it lets the edge produce the
6
+ * error, which is the same message the developer would get in production.
7
+ *
8
+ * A mixed operation (`{ __schema { … } categories { … } }`) is also not
9
+ * introspection — it needs real resolvers, so it goes to the edge and the edge
10
+ * refuses the `__schema` half. That is the honest answer: the edge genuinely can't
11
+ * serve it, and pretending otherwise would return half-null data.
12
+ */
13
+ export declare function isIntrospectionRequest(query: string, operationName?: string): boolean;
@@ -0,0 +1,47 @@
1
+ // Does this operation need answering LOCALLY?
2
+ //
3
+ // The explorer answers introspection itself, from the schema it resolved, because
4
+ // the deployed edge has introspection disabled — that is the whole point of the
5
+ // arrangement. Everything else is proxied to the edge.
6
+ //
7
+ // So this predicate decides which of two very different things happens to a request,
8
+ // and both misclassifications are bad:
9
+ //
10
+ // a real query judged introspection → executed against a schema with NO resolvers,
11
+ // silently returning nulls instead of data
12
+ // introspection judged a real query → forwarded to an edge that refuses it, so
13
+ // GraphiQL shows no docs
14
+ //
15
+ // Hence: parse and inspect the operation, never string-match. A query that merely
16
+ // MENTIONS `__schema` — in a string literal, a variable default, or as an alias —
17
+ // is a real query.
18
+ import { getOperationAST, parse } from "graphql";
19
+ /**
20
+ * True when `query`'s selected operation is a query whose every top-level selection
21
+ * is an introspection meta-field (`__schema`, `__type`, `__typename`).
22
+ *
23
+ * Unparseable input is NOT introspection: forwarding it lets the edge produce the
24
+ * error, which is the same message the developer would get in production.
25
+ *
26
+ * A mixed operation (`{ __schema { … } categories { … } }`) is also not
27
+ * introspection — it needs real resolvers, so it goes to the edge and the edge
28
+ * refuses the `__schema` half. That is the honest answer: the edge genuinely can't
29
+ * serve it, and pretending otherwise would return half-null data.
30
+ */
31
+ export function isIntrospectionRequest(query, operationName) {
32
+ let document;
33
+ try {
34
+ document = parse(query);
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ const operation = getOperationAST(document, operationName ?? undefined);
40
+ if (!operation || operation.operation !== "query")
41
+ return false;
42
+ const selections = operation.selectionSet.selections;
43
+ if (selections.length === 0)
44
+ return false;
45
+ return selections.every((selection) => selection.kind === "Field" && selection.name.value.startsWith("__"));
46
+ }
47
+ //# sourceMappingURL=introspectionRequest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"introspectionRequest.js","sourceRoot":"","sources":["../../../src/lib/tooling/introspectionRequest.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,EAAE;AACF,kFAAkF;AAClF,gFAAgF;AAChF,uDAAuD;AACvD,EAAE;AACF,qFAAqF;AACrF,uCAAuC;AACvC,EAAE;AACF,sFAAsF;AACtF,kFAAkF;AAClF,kFAAkF;AAClF,gEAAgE;AAChE,EAAE;AACF,kFAAkF;AAClF,kFAAkF;AAClF,mBAAmB;AAEnB,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEjD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAa,EAAE,aAAsB;IAC1E,IAAI,QAAQ,CAAC;IACb,IAAI,CAAC;QACH,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,SAAS,GAAG,eAAe,CAAC,QAAQ,EAAE,aAAa,IAAI,SAAS,CAAC,CAAC;IACxE,IAAI,CAAC,SAAS,IAAI,SAAS,CAAC,SAAS,KAAK,OAAO;QAAE,OAAO,KAAK,CAAC;IAEhE,MAAM,UAAU,GAAG,SAAS,CAAC,YAAY,CAAC,UAAU,CAAC;IACrD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAE1C,OAAO,UAAU,CAAC,KAAK,CACrB,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,KAAK,OAAO,IAAI,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CACnF,CAAC;AACJ,CAAC"}