@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.
- package/LICENSE +21 -0
- package/README.md +64 -0
- package/dist/commands/integration-layer/config/get.d.ts +9 -0
- package/dist/commands/integration-layer/config/get.js +22 -0
- package/dist/commands/integration-layer/config/get.js.map +1 -0
- package/dist/commands/integration-layer/config/list.d.ts +6 -0
- package/dist/commands/integration-layer/config/list.js +20 -0
- package/dist/commands/integration-layer/config/list.js.map +1 -0
- package/dist/commands/integration-layer/config/set.d.ts +13 -0
- package/dist/commands/integration-layer/config/set.js +29 -0
- package/dist/commands/integration-layer/config/set.js.map +1 -0
- package/dist/commands/integration-layer/config/unset.d.ts +9 -0
- package/dist/commands/integration-layer/config/unset.js +18 -0
- package/dist/commands/integration-layer/config/unset.js.map +1 -0
- package/dist/commands/integration-layer/extension/build.d.ts +12 -0
- package/dist/commands/integration-layer/extension/build.js +40 -0
- package/dist/commands/integration-layer/extension/build.js.map +1 -0
- package/dist/commands/integration-layer/extension/delete.d.ts +9 -0
- package/dist/commands/integration-layer/extension/delete.js +39 -0
- package/dist/commands/integration-layer/extension/delete.js.map +1 -0
- package/dist/commands/integration-layer/extension/invoke.d.ts +13 -0
- package/dist/commands/integration-layer/extension/invoke.js +89 -0
- package/dist/commands/integration-layer/extension/invoke.js.map +1 -0
- package/dist/commands/integration-layer/extension/push.d.ts +28 -0
- package/dist/commands/integration-layer/extension/push.js +217 -0
- package/dist/commands/integration-layer/extension/push.js.map +1 -0
- package/dist/commands/integration-layer/extension/serve.d.ts +37 -0
- package/dist/commands/integration-layer/extension/serve.js +528 -0
- package/dist/commands/integration-layer/extension/serve.js.map +1 -0
- package/dist/commands/integration-layer/extension/status.d.ts +6 -0
- package/dist/commands/integration-layer/extension/status.js +28 -0
- package/dist/commands/integration-layer/extension/status.js.map +1 -0
- package/dist/commands/integration-layer/extension/validate.d.ts +15 -0
- package/dist/commands/integration-layer/extension/validate.js +98 -0
- package/dist/commands/integration-layer/extension/validate.js.map +1 -0
- package/dist/commands/integration-layer/hello.d.ts +6 -0
- package/dist/commands/integration-layer/hello.js +10 -0
- package/dist/commands/integration-layer/hello.js.map +1 -0
- package/dist/commands/integration-layer/init.d.ts +13 -0
- package/dist/commands/integration-layer/init.js +279 -0
- package/dist/commands/integration-layer/init.js.map +1 -0
- package/dist/commands/integration-layer/schema/fetch.d.ts +9 -0
- package/dist/commands/integration-layer/schema/fetch.js +27 -0
- package/dist/commands/integration-layer/schema/fetch.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/awaitBundleState.d.ts +33 -0
- package/dist/lib/awaitBundleState.js +77 -0
- package/dist/lib/awaitBundleState.js.map +1 -0
- package/dist/lib/base.d.ts +50 -0
- package/dist/lib/base.js +85 -0
- package/dist/lib/base.js.map +1 -0
- package/dist/lib/ilClient.d.ts +93 -0
- package/dist/lib/ilClient.js +120 -0
- package/dist/lib/ilClient.js.map +1 -0
- package/dist/lib/sourceRevision.d.ts +39 -0
- package/dist/lib/sourceRevision.js +95 -0
- package/dist/lib/sourceRevision.js.map +1 -0
- package/dist/lib/tooling/apiExtension.d.ts +31 -0
- package/dist/lib/tooling/apiExtension.js +39 -0
- package/dist/lib/tooling/apiExtension.js.map +1 -0
- package/dist/lib/tooling/build.d.ts +18 -0
- package/dist/lib/tooling/build.js +54 -0
- package/dist/lib/tooling/build.js.map +1 -0
- package/dist/lib/tooling/compose.d.ts +28 -0
- package/dist/lib/tooling/compose.js +48 -0
- package/dist/lib/tooling/compose.js.map +1 -0
- package/dist/lib/tooling/extensions.d.ts +77 -0
- package/dist/lib/tooling/extensions.js +209 -0
- package/dist/lib/tooling/extensions.js.map +1 -0
- package/dist/lib/tooling/gateway.d.ts +21 -0
- package/dist/lib/tooling/gateway.js +65 -0
- package/dist/lib/tooling/gateway.js.map +1 -0
- package/dist/lib/tooling/loadBundle.d.ts +10 -0
- package/dist/lib/tooling/loadBundle.js +27 -0
- package/dist/lib/tooling/loadBundle.js.map +1 -0
- package/dist/lib/tooling/staticAnalysis.d.ts +11 -0
- package/dist/lib/tooling/staticAnalysis.js +167 -0
- package/dist/lib/tooling/staticAnalysis.js.map +1 -0
- package/dist/lib/tooling/validateBundle.d.ts +19 -0
- package/dist/lib/tooling/validateBundle.js +144 -0
- package/dist/lib/tooling/validateBundle.js.map +1 -0
- package/package.json +47 -5
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Flags } from "@oclif/core";
|
|
2
|
+
import { defaultEntry, defaultOutfile } from "../../../lib/tooling/build.js";
|
|
3
|
+
import { bundleForFlags } from "../../../lib/tooling/extensions.js";
|
|
4
|
+
import { validateBundle, BundleValidationError } from "../../../lib/tooling/validateBundle.js";
|
|
5
|
+
import { remoteValidate } from "../../../lib/ilClient.js";
|
|
6
|
+
import { IntegrationLayerCommand } from "../../../lib/base.js";
|
|
7
|
+
export default class ExtensionValidate extends IntegrationLayerCommand {
|
|
8
|
+
static description = "Build, run local checks, then compose against the project schema and gate breaking changes";
|
|
9
|
+
static examples = [
|
|
10
|
+
"<%= config.bin %> integration-layer extension validate",
|
|
11
|
+
"<%= config.bin %> integration-layer extension validate --skip remote",
|
|
12
|
+
"<%= config.bin %> integration-layer extension validate --all",
|
|
13
|
+
];
|
|
14
|
+
static flags = {
|
|
15
|
+
entry: Flags.string({ description: "extension entry source file", default: defaultEntry() }),
|
|
16
|
+
out: Flags.string({ description: "bundle output file", default: defaultOutfile() }),
|
|
17
|
+
all: Flags.boolean({
|
|
18
|
+
description: "validate every extension under ./extensions/* merged into ONE combined bundle (the single deployed artifact)",
|
|
19
|
+
default: false,
|
|
20
|
+
}),
|
|
21
|
+
"extensions-dir": Flags.string({
|
|
22
|
+
description: "directory holding the extension packages (used with --all)",
|
|
23
|
+
default: "extensions",
|
|
24
|
+
}),
|
|
25
|
+
skip: Flags.string({
|
|
26
|
+
description: "run only one half of the gate",
|
|
27
|
+
options: ["local", "remote"],
|
|
28
|
+
}),
|
|
29
|
+
};
|
|
30
|
+
/** Print a remote validation result legibly. Returns true when it passed. */
|
|
31
|
+
reportRemote(result) {
|
|
32
|
+
if (!result.composes) {
|
|
33
|
+
this.logToStderr("✗ extension does not compose with the integration layer supergraph:");
|
|
34
|
+
for (const err of result.compositionErrors)
|
|
35
|
+
this.logToStderr(` - ${err}`);
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
if (result.breakingChanges.length > 0) {
|
|
39
|
+
this.logToStderr("✗ extension introduces breaking changes to the published supergraph:");
|
|
40
|
+
for (const change of result.breakingChanges) {
|
|
41
|
+
this.logToStderr(` - [${change.type}] ${change.description}`);
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
if (!result.comparedToPublished) {
|
|
46
|
+
this.log("✓ composes with the integration layer supergraph (breaking-change check skipped: no published baseline)");
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
this.log("✓ composes with the integration layer supergraph and introduces no breaking changes");
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
async run() {
|
|
53
|
+
const { flags } = await this.parse(ExtensionValidate);
|
|
54
|
+
let outfile;
|
|
55
|
+
let sourceFiles;
|
|
56
|
+
try {
|
|
57
|
+
({ outfile, sourceFiles } = await bundleForFlags({
|
|
58
|
+
all: flags.all,
|
|
59
|
+
extensionsDir: flags["extensions-dir"],
|
|
60
|
+
entry: flags.entry,
|
|
61
|
+
out: flags.out,
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
this.error(err.message);
|
|
66
|
+
}
|
|
67
|
+
let typeDefs = null;
|
|
68
|
+
if (flags.skip !== "local") {
|
|
69
|
+
try {
|
|
70
|
+
const local = await validateBundle(outfile, sourceFiles);
|
|
71
|
+
typeDefs = local.typeDefs;
|
|
72
|
+
this.log(`✓ local checks passed (resolver roots: ${local.resolverTypes.join(", ") || "none"}; ` +
|
|
73
|
+
`API extensions: ${local.apiExtensionKeys.join(", ") || "none"})`);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
if (err instanceof BundleValidationError)
|
|
77
|
+
this.error(err.message);
|
|
78
|
+
throw err;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// Skipping local: still need the SDL for the remote check — load it cheaply.
|
|
83
|
+
const local = await validateBundle(outfile, sourceFiles).catch(() => null);
|
|
84
|
+
typeDefs = local?.typeDefs ?? null;
|
|
85
|
+
}
|
|
86
|
+
if (flags.skip === "remote")
|
|
87
|
+
return;
|
|
88
|
+
if (typeDefs === null) {
|
|
89
|
+
this.log("· no GraphQL subgraph in this bundle — nothing to compose remotely.");
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const { baseUrl, projectKey, token } = await this.resolveIlContext(flags);
|
|
93
|
+
const result = await remoteValidate(baseUrl, projectKey, token, typeDefs);
|
|
94
|
+
if (!this.reportRemote(result))
|
|
95
|
+
this.error("remote validation failed.");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../../../src/commands/integration-layer/extension/validate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Command } from "@oclif/core";
|
|
2
|
+
export default class IntegrationLayerHelloCommand extends Command {
|
|
3
|
+
static description = "Print a hello world message from the integration-layer topic";
|
|
4
|
+
static examples = ["<%= config.bin %> integration-layer hello"];
|
|
5
|
+
async run() {
|
|
6
|
+
await this.parse(IntegrationLayerHelloCommand);
|
|
7
|
+
this.log("Hello, world!");
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=hello.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hello.js","sourceRoot":"","sources":["../../../src/commands/integration-layer/hello.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtC,MAAM,CAAC,OAAO,OAAO,4BAA6B,SAAQ,OAAO;IAC/D,MAAM,CAAU,WAAW,GAAG,8DAA8D,CAAC;IAE7F,MAAM,CAAU,QAAQ,GAAG,CAAC,2CAA2C,CAAC,CAAC;IAEzE,KAAK,CAAC,GAAG;QACP,MAAM,IAAI,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;QAC/C,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IAC5B,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from "@oclif/core";
|
|
2
|
+
export default class Init extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
directory: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
template: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { mkdir, readdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { basename, join, resolve } from "node:path";
|
|
3
|
+
import { Args, Command, Flags } from "@oclif/core";
|
|
4
|
+
// `init` scaffolds a MONOREPO you own and grow — a pnpm workspace whose members are
|
|
5
|
+
// individual extensions (`extensions/*`), seeded with one buildable hello-world
|
|
6
|
+
// extension that extends `Query`. The build/validate/push flow itself lives in this
|
|
7
|
+
// published CLI plugin, not in the scaffold, so an extension package stays tiny (just
|
|
8
|
+
// `src/extension.ts` + a thin package.json/tsconfig). Everything is vendored inline —
|
|
9
|
+
// no network fetch — and kept dependency-light so the extension commands work on it
|
|
10
|
+
// out of the box. The `basic` template is the only one today.
|
|
11
|
+
/** Derive a valid npm package name from the scaffold directory (fallback: a generic one). */
|
|
12
|
+
function projectNameFor(dir) {
|
|
13
|
+
const sanitized = basename(resolve(dir))
|
|
14
|
+
.toLowerCase()
|
|
15
|
+
.replace(/[^a-z0-9-]+/g, "-")
|
|
16
|
+
.replace(/^-+|-+$/g, "");
|
|
17
|
+
return sanitized || "integration-layer-extensions";
|
|
18
|
+
}
|
|
19
|
+
/** The monorepo file set, parameterized by the root package name. */
|
|
20
|
+
function templateFiles(projectName) {
|
|
21
|
+
return {
|
|
22
|
+
"package.json": `{
|
|
23
|
+
"name": ${JSON.stringify(projectName)},
|
|
24
|
+
"private": true,
|
|
25
|
+
"type": "module",
|
|
26
|
+
"packageManager": "pnpm@11.17.0",
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": "22.x || 24.x"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "commercetools integration-layer extension build --all",
|
|
32
|
+
"typecheck": "pnpm -r typecheck",
|
|
33
|
+
"lint": "eslint .",
|
|
34
|
+
"dev": "commercetools integration-layer extension serve --all",
|
|
35
|
+
"validate": "commercetools integration-layer extension validate --all",
|
|
36
|
+
"push": "commercetools integration-layer extension push --all"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"eslint": "^10.8.0",
|
|
40
|
+
"typescript": "~6.0.3",
|
|
41
|
+
"typescript-eslint": "^8.65.0"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
`,
|
|
45
|
+
"pnpm-workspace.yaml": `packages:
|
|
46
|
+
# Each extension is a workspace member (edit its src/extension.ts). A project deploys
|
|
47
|
+
# ONE bundle, so the root \`pnpm build\` / \`validate\` / \`push\` scripts MERGE every
|
|
48
|
+
# extension here into that single combined bundle — never one per package.
|
|
49
|
+
- 'extensions/*'
|
|
50
|
+
|
|
51
|
+
# Refuse dependency versions published less than 24h ago. Minutes; 1440 = 24h.
|
|
52
|
+
minimumReleaseAge: 1440
|
|
53
|
+
`,
|
|
54
|
+
"tsconfig.base.json": `{
|
|
55
|
+
"compilerOptions": {
|
|
56
|
+
"target": "ES2022",
|
|
57
|
+
"module": "NodeNext",
|
|
58
|
+
"moduleResolution": "NodeNext",
|
|
59
|
+
"strict": true,
|
|
60
|
+
"esModuleInterop": true,
|
|
61
|
+
"skipLibCheck": true,
|
|
62
|
+
"forceConsistentCasingInFileNames": true,
|
|
63
|
+
"resolveJsonModule": true
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
`,
|
|
67
|
+
"eslint.config.js": `import tseslint from 'typescript-eslint';
|
|
68
|
+
|
|
69
|
+
export default tseslint.config(
|
|
70
|
+
{ ignores: ['**/dist/**', '**/node_modules/**'] },
|
|
71
|
+
...tseslint.configs.recommended,
|
|
72
|
+
{
|
|
73
|
+
// Allow \`_\`-prefixed unused args (resolver signatures often ignore arguments).
|
|
74
|
+
files: ['**/*.ts'],
|
|
75
|
+
rules: {
|
|
76
|
+
'@typescript-eslint/no-unused-vars': [
|
|
77
|
+
'error',
|
|
78
|
+
{
|
|
79
|
+
argsIgnorePattern: '^_',
|
|
80
|
+
varsIgnorePattern: '^_',
|
|
81
|
+
caughtErrorsIgnorePattern: '^_',
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
);
|
|
87
|
+
`,
|
|
88
|
+
".gitignore": `node_modules/
|
|
89
|
+
dist/
|
|
90
|
+
.env
|
|
91
|
+
`,
|
|
92
|
+
".env.example": `# The authenticated commands (validate/push/status/delete, schema, config) reuse the
|
|
93
|
+
# token from \`commercetools auth login\` (as a manage_project client) — no credentials
|
|
94
|
+
# go here. Optionally override the integration-layer edge URL. Copy to .env.
|
|
95
|
+
INTEGRATION_LAYER_URL=
|
|
96
|
+
`,
|
|
97
|
+
"README.md": `# ${projectName}
|
|
98
|
+
|
|
99
|
+
A monorepo of commercetools integration-layer extensions, scaffolded by
|
|
100
|
+
\`commercetools integration-layer init\`. Each extension is a workspace member under
|
|
101
|
+
\`extensions/\`; the starter one (\`extensions/hello-world\`) adds a \`hello\` field to
|
|
102
|
+
\`Query\`.
|
|
103
|
+
|
|
104
|
+
A project deploys a **single bundle** — one federation subgraph the router composes
|
|
105
|
+
with the integration layer. So this repo's extensions are **merged into that one
|
|
106
|
+
bundle**: the root \`build\` / \`validate\` / \`push\` scripts combine everything under
|
|
107
|
+
\`extensions/\` and push it as one artifact (not one per package), and \`dev\` serves that
|
|
108
|
+
same merged subgraph locally. Split your code into as many \`extensions/*\` packages as
|
|
109
|
+
makes sense to author — they always ship as one.
|
|
110
|
+
|
|
111
|
+
## Set up the CLI (once)
|
|
112
|
+
|
|
113
|
+
The \`build\`/\`serve\`/\`validate\`/\`push\` scripts run through the **commercetools CLI**'s
|
|
114
|
+
\`integration-layer\` topic, added by the published
|
|
115
|
+
\`@commercetools/cli-topic-integration-layer\` plugin. Install the CLI globally with
|
|
116
|
+
pnpm, add the plugin, then log in:
|
|
117
|
+
|
|
118
|
+
\`\`\`sh
|
|
119
|
+
# Install the commercetools CLI globally with pnpm, then add the integration-layer
|
|
120
|
+
# topic. The plugin is on the public npm registry, so no auth or scope mapping is
|
|
121
|
+
# needed. (Install from @dev for now — the \`plugins\` command is only in the CLI's dev
|
|
122
|
+
# prerelease; drop @dev once it ships to @latest.)
|
|
123
|
+
pnpm add -g @commercetools/cli@dev
|
|
124
|
+
commercetools plugins install @commercetools/cli-topic-integration-layer
|
|
125
|
+
|
|
126
|
+
# Log in once — mints the manage_project token the authenticated commands reuse
|
|
127
|
+
# (validate/push/status/delete, schema, config) and sets your target project.
|
|
128
|
+
commercetools auth login --project-key <your-project-key>
|
|
129
|
+
\`\`\`
|
|
130
|
+
|
|
131
|
+
## Getting started
|
|
132
|
+
|
|
133
|
+
Everything runs through pnpm — the scripts below wrap the \`commercetools
|
|
134
|
+
integration-layer extension …\` commands.
|
|
135
|
+
|
|
136
|
+
\`\`\`sh
|
|
137
|
+
pnpm install
|
|
138
|
+
|
|
139
|
+
# From the repo root — these operate on the ONE combined bundle:
|
|
140
|
+
pnpm dev # merge every extension into one subgraph and serve it behind a local
|
|
141
|
+
# federated gateway (+ the integration layer) at
|
|
142
|
+
# http://localhost:4000/graphql — the production topology in miniature.
|
|
143
|
+
# Needs \`commercetools auth login\` (composes against your live schema).
|
|
144
|
+
pnpm validate # compose the combined bundle against the live schema (no upload)
|
|
145
|
+
pnpm push # validate + upload the combined bundle (replaces the project's one)
|
|
146
|
+
|
|
147
|
+
# …or focus on a single extension in isolation (standalone subgraph, no login needed):
|
|
148
|
+
pnpm --filter @extensions/hello-world dev
|
|
149
|
+
\`\`\`
|
|
150
|
+
|
|
151
|
+
With \`pnpm dev\` running, open http://localhost:4000/graphql for a GraphiQL over the
|
|
152
|
+
merged schema; the raw combined extensions subgraph is at \`/_extension\`, and the
|
|
153
|
+
browsable merged (extensions + integration layer) schema at \`/composed\`.
|
|
154
|
+
|
|
155
|
+
Optionally copy \`.env.example\` to \`.env\` to override the integration-layer edge URL.
|
|
156
|
+
|
|
157
|
+
## Adding another extension
|
|
158
|
+
|
|
159
|
+
Copy \`extensions/hello-world\` to \`extensions/<your-extension>\`, rename it in its
|
|
160
|
+
\`package.json\`, and edit its \`src/extension.ts\`. The root scripts discover every folder
|
|
161
|
+
under \`extensions/\` automatically and merge it into the single bundle — so \`pnpm dev\`,
|
|
162
|
+
\`validate\`, and \`push\` all just pick it up, with no port wrangling. (Two extensions
|
|
163
|
+
can each add fields to \`Query\`; they only clash if they declare the *same* field.)
|
|
164
|
+
`,
|
|
165
|
+
"extensions/hello-world/package.json": `{
|
|
166
|
+
"name": "@extensions/hello-world",
|
|
167
|
+
"version": "0.1.0",
|
|
168
|
+
"private": true,
|
|
169
|
+
"type": "module",
|
|
170
|
+
"scripts": {
|
|
171
|
+
"build": "commercetools integration-layer extension build",
|
|
172
|
+
"dev": "commercetools integration-layer extension serve",
|
|
173
|
+
"typecheck": "tsc --noEmit"
|
|
174
|
+
},
|
|
175
|
+
"devDependencies": {
|
|
176
|
+
"@types/node": "^22.20.1",
|
|
177
|
+
"typescript": "~6.0.3"
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
`,
|
|
181
|
+
"extensions/hello-world/tsconfig.json": `{
|
|
182
|
+
"extends": "../../tsconfig.base.json",
|
|
183
|
+
"compilerOptions": {
|
|
184
|
+
"noEmit": true,
|
|
185
|
+
"types": ["node"]
|
|
186
|
+
},
|
|
187
|
+
"include": ["src"],
|
|
188
|
+
"exclude": ["node_modules", "dist"]
|
|
189
|
+
}
|
|
190
|
+
`,
|
|
191
|
+
"extensions/hello-world/src/extension.ts": `/**
|
|
192
|
+
* A hello-world integration-layer extension — the PURELY ADDITIVE pattern: it adds a
|
|
193
|
+
* brand-new root field (\`Query.hello\`) and shares nothing with the integration layer,
|
|
194
|
+
* so the two \`Query\` types merge with no change to it and composition is trivial.
|
|
195
|
+
*
|
|
196
|
+
* An extension exports \`typeDefs\` (a federation-v2 SDL string) + \`resolvers\` and runs
|
|
197
|
+
* in a restricted runtime. Edit this file, then from the repo ROOT run \`pnpm validate\` /
|
|
198
|
+
* \`pnpm push\` — every extension is merged into the one bundle the project deploys (the
|
|
199
|
+
* target project comes from your \`commercetools auth login\`). Iterate on just this one
|
|
200
|
+
* with \`pnpm --filter @extensions/hello-world dev\`.
|
|
201
|
+
*/
|
|
202
|
+
|
|
203
|
+
export const typeDefs = \`
|
|
204
|
+
extend schema @link(url: "https://specs.apollo.dev/federation/v2.3")
|
|
205
|
+
|
|
206
|
+
type Query {
|
|
207
|
+
"A friendly greeting contributed by this extension."
|
|
208
|
+
hello(name: String): String!
|
|
209
|
+
}
|
|
210
|
+
\`;
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* The per-call capability context the restricted runtime hands each resolver.
|
|
214
|
+
* \`now()\` is the current epoch millis (a convenience — \`Date.now()\` works too);
|
|
215
|
+
* \`config\` is this extension's merchant-supplied \`{ key: value }\` map, with secret
|
|
216
|
+
* values decrypted host-side (e.g. \`ctx.config.API_KEY\`). Set values with
|
|
217
|
+
* \`commercetools integration-layer config set <KEY> <VALUE>\`.
|
|
218
|
+
*/
|
|
219
|
+
interface ExtensionContext {
|
|
220
|
+
now(): number;
|
|
221
|
+
config: Readonly<Record<string, string>>;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export const resolvers = {
|
|
225
|
+
Query: {
|
|
226
|
+
// A resolver receives (parent, args, context): \`_parent\` is the object the field
|
|
227
|
+
// hangs off (nothing here — this is a root field), \`args\` are the field arguments
|
|
228
|
+
// from the query, and \`ctx\` exposes the restricted runtime's host capabilities.
|
|
229
|
+
hello: (_parent: unknown, { name }: { name?: string }, ctx: ExtensionContext) => {
|
|
230
|
+
const greeting = ctx.config.GREETING ?? "Hello";
|
|
231
|
+
return \`\${greeting}, \${name ?? "world"}, from your integration-layer extension!\`;
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
`,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
export default class Init extends Command {
|
|
239
|
+
static description = "Scaffold an integration-layer extensions monorepo (a pnpm workspace with one hello-world extension)";
|
|
240
|
+
static examples = [
|
|
241
|
+
"<%= config.bin %> integration-layer init my-extensions",
|
|
242
|
+
"<%= config.bin %> integration-layer init my-extensions --template basic",
|
|
243
|
+
];
|
|
244
|
+
static args = {
|
|
245
|
+
directory: Args.string({ description: "directory to scaffold the monorepo into", required: true }),
|
|
246
|
+
};
|
|
247
|
+
static flags = {
|
|
248
|
+
template: Flags.string({
|
|
249
|
+
description: "the template to use",
|
|
250
|
+
options: ["basic"],
|
|
251
|
+
default: "basic",
|
|
252
|
+
}),
|
|
253
|
+
force: Flags.boolean({
|
|
254
|
+
char: "f",
|
|
255
|
+
description: "scaffold into a non-empty directory",
|
|
256
|
+
default: false,
|
|
257
|
+
}),
|
|
258
|
+
};
|
|
259
|
+
async run() {
|
|
260
|
+
const { args, flags } = await this.parse(Init);
|
|
261
|
+
const dir = args.directory;
|
|
262
|
+
const existing = await readdir(dir).catch(() => []);
|
|
263
|
+
if (existing.length > 0 && !flags.force) {
|
|
264
|
+
this.error(`Directory '${dir}' is not empty. Pass --force to scaffold into it anyway.`);
|
|
265
|
+
}
|
|
266
|
+
const files = templateFiles(projectNameFor(dir));
|
|
267
|
+
for (const [relPath, contents] of Object.entries(files)) {
|
|
268
|
+
const target = join(dir, relPath);
|
|
269
|
+
await mkdir(join(target, ".."), { recursive: true });
|
|
270
|
+
await writeFile(target, contents, "utf8");
|
|
271
|
+
}
|
|
272
|
+
this.log(`✓ scaffolded a '${flags.template}' extensions monorepo in ${dir}`);
|
|
273
|
+
this.log("Next steps:");
|
|
274
|
+
this.log(` cd ${dir}`);
|
|
275
|
+
this.log(" pnpm install");
|
|
276
|
+
this.log(" pnpm dev # run the hello-world extension locally");
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/commands/integration-layer/init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { IntegrationLayerCommand } from "../../../lib/base.js";
|
|
2
|
+
export default class SchemaFetch extends IntegrationLayerCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
out: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
};
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { writeFile } from "node:fs/promises";
|
|
2
|
+
import { Flags } from "@oclif/core";
|
|
3
|
+
import { fetchSubgraphSdl } from "../../../lib/ilClient.js";
|
|
4
|
+
import { IntegrationLayerCommand } from "../../../lib/base.js";
|
|
5
|
+
export default class SchemaFetch extends IntegrationLayerCommand {
|
|
6
|
+
static description = "Print (or write) the project's core-subgraph SDL — the input extensions compose against";
|
|
7
|
+
static examples = [
|
|
8
|
+
"<%= config.bin %> integration-layer schema fetch",
|
|
9
|
+
"<%= config.bin %> integration-layer schema fetch --out subgraph.graphql",
|
|
10
|
+
];
|
|
11
|
+
static flags = {
|
|
12
|
+
out: Flags.string({ description: "write the SDL to this file instead of stdout" }),
|
|
13
|
+
};
|
|
14
|
+
async run() {
|
|
15
|
+
const { flags } = await this.parse(SchemaFetch);
|
|
16
|
+
const { baseUrl, projectKey, token } = await this.resolveIlContext(flags);
|
|
17
|
+
const sdl = await fetchSubgraphSdl(baseUrl, projectKey, token);
|
|
18
|
+
if (flags.out) {
|
|
19
|
+
await writeFile(flags.out, sdl, "utf8");
|
|
20
|
+
this.log(`✓ wrote ${projectKey} core-subgraph SDL → ${flags.out}`);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
this.log(sdl);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=fetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch.js","sourceRoot":"","sources":["../../../../src/commands/integration-layer/schema/fetch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAE/D,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,uBAAuB;IAC9D,MAAM,CAAU,WAAW,GACzB,yFAAyF,CAAC;IAE5F,MAAM,CAAU,QAAQ,GAAG;QACzB,kDAAkD;QAClD,yEAAyE;KAC1E,CAAC;IAEF,MAAM,CAAU,KAAK,GAAG;QACtB,GAAG,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,8CAA8C,EAAE,CAAC;KACnF,CAAC;IAEF,KAAK,CAAC,GAAG;QACP,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QAChD,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;QAC1E,MAAM,GAAG,GAAG,MAAM,gBAAgB,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC;QAC/D,IAAI,KAAK,CAAC,GAAG,EAAE,CAAC;YACd,MAAM,SAAS,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;YACxC,IAAI,CAAC,GAAG,CAAC,WAAW,UAAU,wBAAwB,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;QACrE,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAChB,CAAC;IACH,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { defineApiExtension, approve, block, update, type ApiExtensionAction, type ApiExtensionDefinition, type ApiExtensionError, type ApiExtensionInput, type ApiExtensionResult, type ExtensionContext, type ExtensionInput, } from "./lib/tooling/apiExtension.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// oclif discovers commands from the `oclif.commands` directory (see package.json).
|
|
2
|
+
// The package `main` re-exports the extension authoring API so an author can import
|
|
3
|
+
// the helpers/types when writing `src/extension.ts`.
|
|
4
|
+
export { defineApiExtension, approve, block, update, } from "./lib/tooling/apiExtension.js";
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,oFAAoF;AACpF,qDAAqD;AACrD,OAAO,EACL,kBAAkB,EAClB,OAAO,EACP,KAAK,EACL,MAAM,GAQP,MAAM,+BAA+B,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type ExtensionMeta } from "./ilClient.js";
|
|
2
|
+
/** How the wait ended. Only `failed` means "the push produced a broken bundle". */
|
|
3
|
+
export type BundleOutcome = {
|
|
4
|
+
kind: "running";
|
|
5
|
+
meta: ExtensionMeta;
|
|
6
|
+
} | {
|
|
7
|
+
kind: "failed";
|
|
8
|
+
meta: ExtensionMeta;
|
|
9
|
+
} | {
|
|
10
|
+
kind: "superseded";
|
|
11
|
+
meta: ExtensionMeta;
|
|
12
|
+
} | {
|
|
13
|
+
kind: "unknown";
|
|
14
|
+
reason: string;
|
|
15
|
+
meta?: ExtensionMeta;
|
|
16
|
+
};
|
|
17
|
+
export interface AwaitOptions {
|
|
18
|
+
/** Give up after this long. */
|
|
19
|
+
timeoutMs: number;
|
|
20
|
+
/** Gap between metadata reads. */
|
|
21
|
+
intervalMs: number;
|
|
22
|
+
/** Injected so tests don't actually sleep. */
|
|
23
|
+
sleep?: (ms: number) => Promise<void>;
|
|
24
|
+
/** Injected so tests control elapsed time. */
|
|
25
|
+
now?: () => number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Poll until `version` leaves `pending`, or we run out of time.
|
|
29
|
+
*
|
|
30
|
+
* The first read happens immediately — an extension that was already mid-poll can
|
|
31
|
+
* settle the version before we ever sleep.
|
|
32
|
+
*/
|
|
33
|
+
export declare function awaitBundleState(baseUrl: string, projectKey: string, token: string, version: number, options: AwaitOptions): Promise<BundleOutcome>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Wait for a just-pushed bundle version to prove itself.
|
|
2
|
+
//
|
|
3
|
+
// A push only STORES a bundle. The extension picks it up on its own poll, tries to
|
|
4
|
+
// load it, and reports back — so the version starts `pending` and only becomes
|
|
5
|
+
// `running` (it loaded and published its schema) or `failed` (it didn't, with a
|
|
6
|
+
// reason) a little later. Until this, `push` reported success the moment the bytes
|
|
7
|
+
// landed, which meant a bundle the sandbox refuses to run looked like a clean push.
|
|
8
|
+
//
|
|
9
|
+
// This polls the project's bundle metadata until the pushed version settles, and
|
|
10
|
+
// hands the caller back an outcome it can turn into an exit code.
|
|
11
|
+
//
|
|
12
|
+
// Three things it deliberately does NOT do:
|
|
13
|
+
//
|
|
14
|
+
// - It never treats "I couldn't find out" as a failure. An integration layer that
|
|
15
|
+
// predates bundle state reports no `state` at all, and a project whose extension
|
|
16
|
+
// isn't deployed yet will never move off `pending`. Both are `unknown`, not
|
|
17
|
+
// `failed` — failing a push for them would break perfectly good workflows.
|
|
18
|
+
// - It doesn't watch for the version to change under it. If someone else pushes
|
|
19
|
+
// while we wait, the newest version is no longer ours; we say so and stop rather
|
|
20
|
+
// than report on somebody else's push.
|
|
21
|
+
// - It doesn't retry transient metadata-read errors into a verdict. A read that
|
|
22
|
+
// throws ends the wait as `unknown` with the error attached.
|
|
23
|
+
import { fetchExtensionMeta } from "./ilClient.js";
|
|
24
|
+
const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
25
|
+
/**
|
|
26
|
+
* Poll until `version` leaves `pending`, or we run out of time.
|
|
27
|
+
*
|
|
28
|
+
* The first read happens immediately — an extension that was already mid-poll can
|
|
29
|
+
* settle the version before we ever sleep.
|
|
30
|
+
*/
|
|
31
|
+
export async function awaitBundleState(baseUrl, projectKey, token, version, options) {
|
|
32
|
+
const sleep = options.sleep ?? defaultSleep;
|
|
33
|
+
const now = options.now ?? Date.now;
|
|
34
|
+
const deadline = now() + options.timeoutMs;
|
|
35
|
+
for (;;) {
|
|
36
|
+
let meta;
|
|
37
|
+
try {
|
|
38
|
+
meta = await fetchExtensionMeta(baseUrl, projectKey, token);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
return {
|
|
42
|
+
kind: "unknown",
|
|
43
|
+
reason: `could not read the stored bundle's status: ${err.message}`,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
if (meta === null) {
|
|
47
|
+
// Deleted out from under us between the push and this read.
|
|
48
|
+
return { kind: "unknown", reason: "no bundle is stored for this project any more" };
|
|
49
|
+
}
|
|
50
|
+
if (meta.state === undefined) {
|
|
51
|
+
// An integration layer that doesn't report state. Nothing to wait for.
|
|
52
|
+
return {
|
|
53
|
+
kind: "unknown",
|
|
54
|
+
reason: "this integration layer does not report bundle status",
|
|
55
|
+
meta,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (meta.version !== version) {
|
|
59
|
+
// Someone else pushed while we were waiting; the newest version isn't ours.
|
|
60
|
+
return { kind: "superseded", meta };
|
|
61
|
+
}
|
|
62
|
+
if (meta.state === "running")
|
|
63
|
+
return { kind: "running", meta };
|
|
64
|
+
if (meta.state === "failed")
|
|
65
|
+
return { kind: "failed", meta };
|
|
66
|
+
// Still pending.
|
|
67
|
+
if (now() >= deadline) {
|
|
68
|
+
return {
|
|
69
|
+
kind: "unknown",
|
|
70
|
+
reason: `the extension did not report on version ${version} within ${Math.round(options.timeoutMs / 1000)}s`,
|
|
71
|
+
meta,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
await sleep(options.intervalMs);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=awaitBundleState.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"awaitBundleState.js","sourceRoot":"","sources":["../../src/lib/awaitBundleState.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,EAAE;AACF,mFAAmF;AACnF,+EAA+E;AAC/E,gFAAgF;AAChF,mFAAmF;AACnF,oFAAoF;AACpF,EAAE;AACF,iFAAiF;AACjF,kEAAkE;AAClE,EAAE;AACF,4CAA4C;AAC5C,EAAE;AACF,oFAAoF;AACpF,qFAAqF;AACrF,gFAAgF;AAChF,+EAA+E;AAC/E,kFAAkF;AAClF,qFAAqF;AACrF,2CAA2C;AAC3C,kFAAkF;AAClF,iEAAiE;AAEjE,OAAO,EAAE,kBAAkB,EAAsB,MAAM,eAAe,CAAC;AAoBvE,MAAM,YAAY,GAAG,CAAC,EAAU,EAAiB,EAAE,CACjD,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAEpD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,OAAe,EACf,UAAkB,EAClB,KAAa,EACb,OAAe,EACf,OAAqB;IAErB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,YAAY,CAAC;IAC5C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACpC,MAAM,QAAQ,GAAG,GAAG,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC;IAE3C,SAAS,CAAC;QACR,IAAI,IAA0B,CAAC;QAC/B,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,kBAAkB,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC;QAC9D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO;gBACL,IAAI,EAAE,SAAS;gBACf,MAAM,EAAE,8CAA+C,GAAa,CAAC,OAAO,EAAE;aAC/E,CAAC;QACJ,CAAC;QAED,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,4DAA4D;YAC5D,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,+CAA+C,EAAE,CAAC;QACtF,CAAC;QACD,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YAC7B,uEAAuE;YACvE,OAAO;gBACL,IAAI,EAAE,SAAS;gBACf,MAAM,EAAE,sDAAsD;gBAC9D,IAAI;aACL,CAAC;QACJ,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC;YAC7B,4EAA4E;YAC5E,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;QACtC,CAAC;QACD,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS;YAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;QAC/D,IAAI,IAAI,CAAC,KAAK,KAAK,QAAQ;YAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QAE7D,iBAAiB;QACjB,IAAI,GAAG,EAAE,IAAI,QAAQ,EAAE,CAAC;YACtB,OAAO;gBACL,IAAI,EAAE,SAAS;gBACf,MAAM,EAAE,2CAA2C,OAAO,WAAW,IAAI,CAAC,KAAK,CAC7E,OAAO,CAAC,SAAS,GAAG,IAAI,CACzB,GAAG;gBACJ,IAAI;aACL,CAAC;QACJ,CAAC;QACD,MAAM,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAClC,CAAC;AACH,CAAC"}
|