@commercetools/cli-topic-integration-layer 0.0.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +64 -0
  3. package/dist/commands/integration-layer/config/get.d.ts +9 -0
  4. package/dist/commands/integration-layer/config/get.js +22 -0
  5. package/dist/commands/integration-layer/config/get.js.map +1 -0
  6. package/dist/commands/integration-layer/config/list.d.ts +6 -0
  7. package/dist/commands/integration-layer/config/list.js +20 -0
  8. package/dist/commands/integration-layer/config/list.js.map +1 -0
  9. package/dist/commands/integration-layer/config/set.d.ts +13 -0
  10. package/dist/commands/integration-layer/config/set.js +29 -0
  11. package/dist/commands/integration-layer/config/set.js.map +1 -0
  12. package/dist/commands/integration-layer/config/unset.d.ts +9 -0
  13. package/dist/commands/integration-layer/config/unset.js +18 -0
  14. package/dist/commands/integration-layer/config/unset.js.map +1 -0
  15. package/dist/commands/integration-layer/extension/build.d.ts +12 -0
  16. package/dist/commands/integration-layer/extension/build.js +40 -0
  17. package/dist/commands/integration-layer/extension/build.js.map +1 -0
  18. package/dist/commands/integration-layer/extension/delete.d.ts +9 -0
  19. package/dist/commands/integration-layer/extension/delete.js +39 -0
  20. package/dist/commands/integration-layer/extension/delete.js.map +1 -0
  21. package/dist/commands/integration-layer/extension/invoke.d.ts +13 -0
  22. package/dist/commands/integration-layer/extension/invoke.js +89 -0
  23. package/dist/commands/integration-layer/extension/invoke.js.map +1 -0
  24. package/dist/commands/integration-layer/extension/push.d.ts +28 -0
  25. package/dist/commands/integration-layer/extension/push.js +217 -0
  26. package/dist/commands/integration-layer/extension/push.js.map +1 -0
  27. package/dist/commands/integration-layer/extension/serve.d.ts +37 -0
  28. package/dist/commands/integration-layer/extension/serve.js +528 -0
  29. package/dist/commands/integration-layer/extension/serve.js.map +1 -0
  30. package/dist/commands/integration-layer/extension/status.d.ts +6 -0
  31. package/dist/commands/integration-layer/extension/status.js +28 -0
  32. package/dist/commands/integration-layer/extension/status.js.map +1 -0
  33. package/dist/commands/integration-layer/extension/validate.d.ts +15 -0
  34. package/dist/commands/integration-layer/extension/validate.js +98 -0
  35. package/dist/commands/integration-layer/extension/validate.js.map +1 -0
  36. package/dist/commands/integration-layer/hello.d.ts +6 -0
  37. package/dist/commands/integration-layer/hello.js +10 -0
  38. package/dist/commands/integration-layer/hello.js.map +1 -0
  39. package/dist/commands/integration-layer/init.d.ts +13 -0
  40. package/dist/commands/integration-layer/init.js +279 -0
  41. package/dist/commands/integration-layer/init.js.map +1 -0
  42. package/dist/commands/integration-layer/schema/fetch.d.ts +9 -0
  43. package/dist/commands/integration-layer/schema/fetch.js +27 -0
  44. package/dist/commands/integration-layer/schema/fetch.js.map +1 -0
  45. package/dist/index.d.ts +1 -0
  46. package/dist/index.js +5 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/lib/awaitBundleState.d.ts +33 -0
  49. package/dist/lib/awaitBundleState.js +77 -0
  50. package/dist/lib/awaitBundleState.js.map +1 -0
  51. package/dist/lib/base.d.ts +50 -0
  52. package/dist/lib/base.js +85 -0
  53. package/dist/lib/base.js.map +1 -0
  54. package/dist/lib/ilClient.d.ts +93 -0
  55. package/dist/lib/ilClient.js +120 -0
  56. package/dist/lib/ilClient.js.map +1 -0
  57. package/dist/lib/sourceRevision.d.ts +39 -0
  58. package/dist/lib/sourceRevision.js +95 -0
  59. package/dist/lib/sourceRevision.js.map +1 -0
  60. package/dist/lib/tooling/apiExtension.d.ts +31 -0
  61. package/dist/lib/tooling/apiExtension.js +39 -0
  62. package/dist/lib/tooling/apiExtension.js.map +1 -0
  63. package/dist/lib/tooling/build.d.ts +18 -0
  64. package/dist/lib/tooling/build.js +54 -0
  65. package/dist/lib/tooling/build.js.map +1 -0
  66. package/dist/lib/tooling/compose.d.ts +28 -0
  67. package/dist/lib/tooling/compose.js +48 -0
  68. package/dist/lib/tooling/compose.js.map +1 -0
  69. package/dist/lib/tooling/extensions.d.ts +77 -0
  70. package/dist/lib/tooling/extensions.js +209 -0
  71. package/dist/lib/tooling/extensions.js.map +1 -0
  72. package/dist/lib/tooling/gateway.d.ts +21 -0
  73. package/dist/lib/tooling/gateway.js +65 -0
  74. package/dist/lib/tooling/gateway.js.map +1 -0
  75. package/dist/lib/tooling/loadBundle.d.ts +10 -0
  76. package/dist/lib/tooling/loadBundle.js +27 -0
  77. package/dist/lib/tooling/loadBundle.js.map +1 -0
  78. package/dist/lib/tooling/staticAnalysis.d.ts +11 -0
  79. package/dist/lib/tooling/staticAnalysis.js +167 -0
  80. package/dist/lib/tooling/staticAnalysis.js.map +1 -0
  81. package/dist/lib/tooling/validateBundle.d.ts +19 -0
  82. package/dist/lib/tooling/validateBundle.js +144 -0
  83. package/dist/lib/tooling/validateBundle.js.map +1 -0
  84. package/package.json +47 -5
@@ -0,0 +1,54 @@
1
+ // Bundle an extension source into a single self-contained CommonJS artifact.
2
+ //
3
+ // Copied from commercetools/integration-layer-extension-examples
4
+ // (packages/tooling/src/build.ts) — a LOW-LEVEL function reused verbatim; the
5
+ // argv/env-driven `buildCommand` wrapper is NOT copied (this plugin's oclif command
6
+ // is the wrapper). esbuild bundles local helper modules inline. The output is
7
+ // CommonJS so the runtime can evaluate it as a script and read `module.exports`.
8
+ // `platform: "neutral"` keeps Node built-ins out (an import of one fails the build,
9
+ // not at load time), and `conditions: ["worker"]` steers a bundled SDK to its
10
+ // fetch-based build (the runtime provides a global `fetch`, not Node `http`/`https`).
11
+ // `graphql` stays EXTERNAL — the runtime supplies its single instance; a second
12
+ // inlined copy would break its `instanceof` checks.
13
+ import { build } from "esbuild";
14
+ import { join, resolve } from "node:path";
15
+ import process from "node:process";
16
+ /** Peer modules the runtime owns — bundled never, resolved at load time. */
17
+ export const HOST_PROVIDED_EXTERNALS = ["graphql"];
18
+ /** The extension source of the example the tool is invoked inside (cwd-relative). */
19
+ export function defaultEntry() {
20
+ return join(process.cwd(), "src", "extension.ts");
21
+ }
22
+ /** The bundle output for the example the tool is invoked inside (cwd-relative). */
23
+ export function defaultOutfile() {
24
+ return join(process.cwd(), "dist", "extension.js");
25
+ }
26
+ /** Bundle `entry` into a single self-contained CJS file at `outfile`. */
27
+ export async function buildBundle(entry = defaultEntry(), outfile = defaultOutfile()) {
28
+ const result = await build({
29
+ entryPoints: [entry],
30
+ outfile,
31
+ bundle: true,
32
+ format: "cjs",
33
+ platform: "neutral",
34
+ target: "node22",
35
+ conditions: ["worker"],
36
+ // `platform: "neutral"` leaves `mainFields` empty, so a package that ships only
37
+ // classic `main`/`module` fields (no `exports`) — e.g. `@commercetools/ts-client`
38
+ // / `@commercetools/platform-sdk`, which an extension reaches for to read CT from
39
+ // a resolver — is otherwise unresolvable ("Could not resolve"). Preferring
40
+ // `module` (the fetch-based ESM build, no Node built-ins) then `main` fixes that;
41
+ // packages that DO declare `exports` (e.g. algoliasearch) resolve through it and
42
+ // ignore this. (ILEE's build.ts lacks this line — its examples don't hit the case;
43
+ // keep the two in sync when it's added there.)
44
+ mainFields: ["module", "main"],
45
+ external: HOST_PROVIDED_EXTERNALS,
46
+ metafile: true,
47
+ logLevel: "silent",
48
+ });
49
+ const sourceFiles = Object.keys(result.metafile.inputs)
50
+ .filter((input) => !input.includes("node_modules"))
51
+ .map((input) => resolve(process.cwd(), input));
52
+ return { outfile, sourceFiles };
53
+ }
54
+ //# sourceMappingURL=build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../../../src/lib/tooling/build.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,EAAE;AACF,iEAAiE;AACjE,8EAA8E;AAC9E,oFAAoF;AACpF,8EAA8E;AAC9E,iFAAiF;AACjF,oFAAoF;AACpF,8EAA8E;AAC9E,sFAAsF;AACtF,gFAAgF;AAChF,oDAAoD;AAEpD,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,OAAO,MAAM,cAAc,CAAC;AAEnC,4EAA4E;AAC5E,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,SAAS,CAAC,CAAC;AAEnD,qFAAqF;AACrF,MAAM,UAAU,YAAY;IAC1B,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC;AACpD,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,cAAc;IAC5B,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;AACrD,CAAC;AAaD,yEAAyE;AACzE,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,QAAgB,YAAY,EAAE,EAC9B,UAAkB,cAAc,EAAE;IAElC,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC;QACzB,WAAW,EAAE,CAAC,KAAK,CAAC;QACpB,OAAO;QACP,MAAM,EAAE,IAAI;QACZ,MAAM,EAAE,KAAK;QACb,QAAQ,EAAE,SAAS;QACnB,MAAM,EAAE,QAAQ;QAChB,UAAU,EAAE,CAAC,QAAQ,CAAC;QACtB,gFAAgF;QAChF,kFAAkF;QAClF,kFAAkF;QAClF,2EAA2E;QAC3E,kFAAkF;QAClF,iFAAiF;QACjF,mFAAmF;QACnF,+CAA+C;QAC/C,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC9B,QAAQ,EAAE,uBAAuB;QACjC,QAAQ,EAAE,IAAI;QACd,QAAQ,EAAE,QAAQ;KACnB,CAAC,CAAC;IACH,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;SACpD,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;SAClD,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC;IACjD,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;AAClC,CAAC"}
@@ -0,0 +1,28 @@
1
+ import { type GraphQLSchema } from "graphql";
2
+ /** The subgraph names the integration layer composes under — matched here so the
3
+ * local composition mirrors the deployed one. `integration-layer` is the name an
4
+ * extension's `@override(from: …)` references. */
5
+ export declare const INTEGRATION_LAYER_SERVICE = "integration-layer";
6
+ export declare const EXTENSION_SERVICE = "extensions";
7
+ export type ComposeResult = {
8
+ ok: true;
9
+ /** The client-facing API schema (federation directives stripped), browsable. */
10
+ apiSchema: GraphQLSchema;
11
+ /** Printed API-schema SDL — "what the full schema looks like" to a consumer. */
12
+ apiSdl: string;
13
+ /** The raw composed supergraph SDL the router plans against. */
14
+ supergraphSdl: string;
15
+ } | {
16
+ ok: false;
17
+ errors: string[];
18
+ };
19
+ /**
20
+ * Compose the integration layer + the extension into a supergraph and derive the
21
+ * client-facing API schema. Never throws — a composition failure comes back in
22
+ * `errors` so the dev server can surface it and keep serving. The URLs are baked into
23
+ * the supergraph so `--gateway` routes correctly; for `--compose` they're cosmetic.
24
+ */
25
+ export declare function composeWithIntegrationLayer(integrationLayerSdl: string, extensionSdl: string, urls: {
26
+ integrationLayerUrl: string;
27
+ extensionUrl: string;
28
+ }): ComposeResult;
@@ -0,0 +1,48 @@
1
+ // Compose the local extension subgraph with the project's live integration-layer
2
+ // subgraph — the same two-subgraph composition the integration layer runs on publish.
3
+ //
4
+ // The LOW-LEVEL `composeWithIntegrationLayer` is copied verbatim from
5
+ // commercetools/integration-layer-extension-examples (packages/tooling/src/compose.ts).
6
+ // The env/token-minting `fetchIntegrationLayerSubgraphSdl` wrapper is NOT copied —
7
+ // fetching the project's subgraph SDL is a plain authenticated HTTP call, so it lives
8
+ // in `../ilClient.ts` parameterised by (baseUrl, projectKey, token), and the command
9
+ // layer supplies the token.
10
+ import { parse, printSchema } from "graphql";
11
+ import { composeServices } from "@apollo/composition";
12
+ /** The subgraph names the integration layer composes under — matched here so the
13
+ * local composition mirrors the deployed one. `integration-layer` is the name an
14
+ * extension's `@override(from: …)` references. */
15
+ export const INTEGRATION_LAYER_SERVICE = "integration-layer";
16
+ export const EXTENSION_SERVICE = "extensions";
17
+ /**
18
+ * Compose the integration layer + the extension into a supergraph and derive the
19
+ * client-facing API schema. Never throws — a composition failure comes back in
20
+ * `errors` so the dev server can surface it and keep serving. The URLs are baked into
21
+ * the supergraph so `--gateway` routes correctly; for `--compose` they're cosmetic.
22
+ */
23
+ export function composeWithIntegrationLayer(integrationLayerSdl, extensionSdl, urls) {
24
+ let services;
25
+ try {
26
+ services = [
27
+ { name: INTEGRATION_LAYER_SERVICE, typeDefs: parse(integrationLayerSdl), url: urls.integrationLayerUrl },
28
+ { name: EXTENSION_SERVICE, typeDefs: parse(extensionSdl), url: urls.extensionUrl },
29
+ ];
30
+ }
31
+ catch (err) {
32
+ return { ok: false, errors: [`SDL is not valid GraphQL: ${err.message}`] };
33
+ }
34
+ const result = composeServices(services);
35
+ // `errors` is the discriminant of the composition-failure variant; its presence
36
+ // narrows `result` to the success variant (with `schema` + `supergraphSdl`).
37
+ if (result.errors) {
38
+ return { ok: false, errors: result.errors.map((e) => e.message) };
39
+ }
40
+ const apiSchema = result.schema.toAPISchema().toGraphQLJSSchema();
41
+ return {
42
+ ok: true,
43
+ apiSchema,
44
+ apiSdl: printSchema(apiSchema),
45
+ supergraphSdl: result.supergraphSdl,
46
+ };
47
+ }
48
+ //# sourceMappingURL=compose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose.js","sourceRoot":"","sources":["../../../src/lib/tooling/compose.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,sFAAsF;AACtF,EAAE;AACF,sEAAsE;AACtE,wFAAwF;AACxF,mFAAmF;AACnF,sFAAsF;AACtF,qFAAqF;AACrF,4BAA4B;AAE5B,OAAO,EAAE,KAAK,EAAE,WAAW,EAAsB,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD;;mDAEmD;AACnD,MAAM,CAAC,MAAM,yBAAyB,GAAG,mBAAmB,CAAC;AAC7D,MAAM,CAAC,MAAM,iBAAiB,GAAG,YAAY,CAAC;AAc9C;;;;;GAKG;AACH,MAAM,UAAU,2BAA2B,CACzC,mBAA2B,EAC3B,YAAoB,EACpB,IAA2D;IAE3D,IAAI,QAAQ,CAAC;IACb,IAAI,CAAC;QACH,QAAQ,GAAG;YACT,EAAE,IAAI,EAAE,yBAAyB,EAAE,QAAQ,EAAE,KAAK,CAAC,mBAAmB,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,mBAAmB,EAAE;YACxG,EAAE,IAAI,EAAE,iBAAiB,EAAE,QAAQ,EAAE,KAAK,CAAC,YAAY,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,YAAY,EAAE;SACnF,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,6BAA8B,GAAa,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;IACxF,CAAC;IAED,MAAM,MAAM,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IACzC,gFAAgF;IAChF,6EAA6E;IAC7E,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IACpE,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,iBAAiB,EAAE,CAAC;IAClE,OAAO;QACL,EAAE,EAAE,IAAI;QACR,SAAS;QACT,MAAM,EAAE,WAAW,CAAC,SAAS,CAAC;QAC9B,aAAa,EAAE,MAAM,CAAC,aAAa;KACpC,CAAC;AACJ,CAAC"}
@@ -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,21 @@
1
+ import { ApolloGateway } from "@apollo/gateway";
2
+ /**
3
+ * Mint an anonymous session and return its bearer token. The gateway attaches it to
4
+ * every integration-layer subgraph request; the integration layer 403s a bearer
5
+ * whose project doesn't match the URL, so an anonymous session is the minimum needed
6
+ * to route real queries through. (A 30-day sliding TTL easily outlasts a dev session.)
7
+ */
8
+ export declare function mintAnonymousSession(integrationLayerUrl: string, projectKey: string): Promise<string>;
9
+ export interface GatewayOptions {
10
+ /** The composed supergraph the planner routes against (from `composeWithIntegrationLayer`). */
11
+ supergraphSdl: string;
12
+ /** Anonymous session bearer, attached to the integration-layer data source's requests. */
13
+ integrationLayerBearer: string;
14
+ }
15
+ /**
16
+ * Build and load an Apollo Gateway over the composed supergraph. Each subgraph is an
17
+ * HTTP data source addressed by its baked URL; the integration-layer one carries the
18
+ * session bearer. Resolves once the gateway has loaded its query planner. Call
19
+ * `.stop()` on the returned gateway when replacing it (on a schema change).
20
+ */
21
+ export declare function makeGateway(opts: GatewayOptions): Promise<ApolloGateway>;
@@ -0,0 +1,65 @@
1
+ // The `--gateway` half of the local dev server: an executable merged schema that
2
+ // routes each field to its owner — the local extension and the project's deployed
3
+ // integration layer — so a query like `{ product(id:…) { name loyaltyPoints(price:…) }}`
4
+ // resolves `name` from the integration layer and `loyaltyPoints` from the local
5
+ // extension in one request, the production topology in miniature.
6
+ //
7
+ // Copied verbatim from commercetools/integration-layer-extension-examples
8
+ // (packages/tooling/src/gateway.ts). We use Apollo Gateway (the reference Federation
9
+ // implementation) because the extension model leans on `@interfaceObject` and only
10
+ // the reference gateway resolves that correctly. Both subgraphs are reached over
11
+ // HTTP, so the extension runs as a genuine subgraph; the integration-layer data
12
+ // source carries an anonymous session bearer (the integration layer re-validates the
13
+ // bearer's project against the URL).
14
+ import { ApolloGateway, RemoteGraphQLDataSource } from "@apollo/gateway";
15
+ import { INTEGRATION_LAYER_SERVICE } from "./compose.js";
16
+ /**
17
+ * Mint an anonymous session and return its bearer token. The gateway attaches it to
18
+ * every integration-layer subgraph request; the integration layer 403s a bearer
19
+ * whose project doesn't match the URL, so an anonymous session is the minimum needed
20
+ * to route real queries through. (A 30-day sliding TTL easily outlasts a dev session.)
21
+ */
22
+ export async function mintAnonymousSession(integrationLayerUrl, projectKey) {
23
+ const base = integrationLayerUrl.replace(/\/+$/, "");
24
+ const url = `${base}/${encodeURIComponent(projectKey)}/session`;
25
+ const res = await fetch(url, {
26
+ method: "POST",
27
+ headers: { "content-type": "application/json" },
28
+ body: JSON.stringify({ grant_type: "anonymous" }),
29
+ });
30
+ const text = await res.text();
31
+ if (!res.ok) {
32
+ throw new Error(`could not mint an anonymous session (${res.status}) at ${url}: ${text}`);
33
+ }
34
+ const token = JSON.parse(text).token;
35
+ if (!token)
36
+ throw new Error("token response had no `token`");
37
+ return token;
38
+ }
39
+ /**
40
+ * Build and load an Apollo Gateway over the composed supergraph. Each subgraph is an
41
+ * HTTP data source addressed by its baked URL; the integration-layer one carries the
42
+ * session bearer. Resolves once the gateway has loaded its query planner. Call
43
+ * `.stop()` on the returned gateway when replacing it (on a schema change).
44
+ */
45
+ export async function makeGateway(opts) {
46
+ const gateway = new ApolloGateway({
47
+ supergraphSdl: opts.supergraphSdl,
48
+ buildService: ({ name, url }) => new RemoteGraphQLDataSource({
49
+ url,
50
+ // Use Node's global `fetch` (undici) for every data source; Apollo Gateway's
51
+ // default fetcher breaks its agent selection in some environments.
52
+ fetcher: globalThis.fetch,
53
+ willSendRequest: ({ request }) => {
54
+ // Authenticate only the integration-layer subgraph; the extension subgraph
55
+ // is the dev server's own internal endpoint and needs no bearer.
56
+ if (name === INTEGRATION_LAYER_SERVICE) {
57
+ request.http?.headers.set("authorization", `Bearer ${opts.integrationLayerBearer}`);
58
+ }
59
+ },
60
+ }),
61
+ });
62
+ await gateway.load();
63
+ return gateway;
64
+ }
65
+ //# sourceMappingURL=gateway.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway.js","sourceRoot":"","sources":["../../../src/lib/tooling/gateway.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,kFAAkF;AAClF,yFAAyF;AACzF,gFAAgF;AAChF,kEAAkE;AAClE,EAAE;AACF,0EAA0E;AAC1E,qFAAqF;AACrF,mFAAmF;AACnF,iFAAiF;AACjF,gFAAgF;AAChF,qFAAqF;AACrF,qCAAqC;AAErC,OAAO,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AACzE,OAAO,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AAEzD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,mBAA2B,EAC3B,UAAkB;IAElB,MAAM,IAAI,GAAG,mBAAmB,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACrD,MAAM,GAAG,GAAG,GAAG,IAAI,IAAI,kBAAkB,CAAC,UAAU,CAAC,UAAU,CAAC;IAChE,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAC3B,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC;KAClD,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,wCAAwC,GAAG,CAAC,MAAM,QAAQ,GAAG,KAAK,IAAI,EAAE,CAAC,CAAC;IAC5F,CAAC;IACD,MAAM,KAAK,GAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAwB,CAAC,KAAK,CAAC;IAC7D,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IAC7D,OAAO,KAAK,CAAC;AACf,CAAC;AASD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAoB;IACpD,MAAM,OAAO,GAAG,IAAI,aAAa,CAAC;QAChC,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,YAAY,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,CAC9B,IAAI,uBAAuB,CAAC;YAC1B,GAAG;YACH,6EAA6E;YAC7E,mEAAmE;YACnE,OAAO,EAAE,UAAU,CAAC,KAAsD;YAC1E,eAAe,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE;gBAC/B,2EAA2E;gBAC3E,iEAAiE;gBACjE,IAAI,IAAI,KAAK,yBAAyB,EAAE,CAAC;oBACvC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,IAAI,CAAC,sBAAsB,EAAE,CAAC,CAAC;gBACtF,CAAC;YACH,CAAC;SACF,CAAC;KACL,CAAC,CAAC;IACH,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;IACrB,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,10 @@
1
+ export interface EvaluatedBundle {
2
+ typeDefs?: unknown;
3
+ resolvers?: unknown;
4
+ apiExtensions?: unknown;
5
+ }
6
+ /**
7
+ * Evaluate an esbuild-CJS extension bundle and return its `module.exports`.
8
+ * `require` resolves only `graphql`; any other module id throws.
9
+ */
10
+ export declare function loadBundleSource(source: string): EvaluatedBundle;
@@ -0,0 +1,27 @@
1
+ // Load a built extension bundle and return its exported `typeDefs` + `resolvers`,
2
+ // used by the resolver-coherence check and the local dev server.
3
+ //
4
+ // Copied verbatim from commercetools/integration-layer-extension-examples
5
+ // (packages/tooling/src/loadBundle.ts). The bundle is esbuild's single-file
6
+ // CommonJS artifact, so we evaluate it with the CommonJS calling convention and a
7
+ // `require` that resolves only `graphql` (a second copy would break `instanceof`).
8
+ // It runs as an ordinary module — not a security boundary; the runtime enforces
9
+ // that, and `staticAnalysis.ts` screens the source first.
10
+ import { GraphQLError } from "graphql";
11
+ /**
12
+ * Evaluate an esbuild-CJS extension bundle and return its `module.exports`.
13
+ * `require` resolves only `graphql`; any other module id throws.
14
+ */
15
+ export function loadBundleSource(source) {
16
+ const moduleObject = { exports: {} };
17
+ const requireShim = (id) => {
18
+ if (id === "graphql")
19
+ return { GraphQLError };
20
+ throw new Error(`extension bundle may not require "${id}"`);
21
+ };
22
+ // The CommonJS wrapper: bind `module`/`exports`/`require` and run the source.
23
+ const evaluate = new Function("module", "exports", "require", source);
24
+ evaluate(moduleObject, moduleObject.exports, requireShim);
25
+ return moduleObject.exports;
26
+ }
27
+ //# sourceMappingURL=loadBundle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loadBundle.js","sourceRoot":"","sources":["../../../src/lib/tooling/loadBundle.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,iEAAiE;AACjE,EAAE;AACF,0EAA0E;AAC1E,4EAA4E;AAC5E,kFAAkF;AAClF,mFAAmF;AACnF,gFAAgF;AAChF,0DAA0D;AAE1D,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAUvC;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,MAAM,YAAY,GAAiC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACnE,MAAM,WAAW,GAAG,CAAC,EAAU,EAAW,EAAE;QAC1C,IAAI,EAAE,KAAK,SAAS;YAAE,OAAO,EAAE,YAAY,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,qCAAqC,EAAE,GAAG,CAAC,CAAC;IAC9D,CAAC,CAAC;IACF,8EAA8E;IAC9E,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;IACtE,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAC1D,OAAO,YAAY,CAAC,OAAO,CAAC;AAC9B,CAAC"}
@@ -0,0 +1,11 @@
1
+ export interface AnalysisIssue {
2
+ file: string;
3
+ line: number;
4
+ message: string;
5
+ }
6
+ /**
7
+ * Analyse the given source files (the author's entry + its local imports) and
8
+ * return every runtime-incompatibility issue found. An empty array means the source
9
+ * is clean of the patterns this checks.
10
+ */
11
+ export declare function analyzeSources(files: string[]): Promise<AnalysisIssue[]>;