From cfc46fbda6875acf08bcf9c31871993484c8929d Mon Sep 17 00:00:00 2001 From: Marjukka Niinioja Date: Tue, 7 Apr 2026 11:42:09 +0300 Subject: [PATCH 1/3] cleaning up paths to not include method-data repo root in create-apiops when installed as standalone package --- bin/method-cli.js | 7 + package-lock.json | 2 +- package.json | 2 + .../bin/create-apiops-project.js | 10 +- packages/create-apiops/bin/method-cli.js | 15 +- packages/create-apiops/template/package.json | 14 +- .../template/specs/openapi/api.yaml | 272 ++++++++++++++++++ scripts/check-package-contents.mjs | 4 +- scripts/test-create-apiops-scaffold.mjs | 5 +- scripts/test-method-cli.mjs | 2 +- 10 files changed, 307 insertions(+), 26 deletions(-) create mode 100644 bin/method-cli.js create mode 100644 packages/create-apiops/template/specs/openapi/api.yaml diff --git a/bin/method-cli.js b/bin/method-cli.js new file mode 100644 index 0000000..a34b73f --- /dev/null +++ b/bin/method-cli.js @@ -0,0 +1,7 @@ +#!/usr/bin/env node +import { main } from "../packages/create-apiops/bin/method-cli.js"; + +main().catch((error) => { + console.error(error instanceof Error ? error.message : error); + process.exit(1); +}); diff --git a/package-lock.json b/package-lock.json index 4846868..91fafd5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1333,7 +1333,7 @@ } }, "packages/create-apiops": { - "version": "1.0.0", + "version": "1.1.2", "hasInstallScript": true, "license": "Apache-2.0", "bin": { diff --git a/package.json b/package.json index 4c07c35..8f23bf1 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "license": "Apache-2.0", "type": "module", "files": [ + "bin", "skills", "AGENTS.md", "src/lib", @@ -18,6 +19,7 @@ "packages/*" ], "exports": { + "./method-cli": "./bin/method-cli.js", "./method-engine": "./src/lib/method-engine.js", "./snippet-engine": "./src/lib/snippet-engine.js", "./canvasData.json": "./src/data/canvas/canvasData.json", diff --git a/packages/create-apiops/bin/create-apiops-project.js b/packages/create-apiops/bin/create-apiops-project.js index f0d6e27..72b2fba 100644 --- a/packages/create-apiops/bin/create-apiops-project.js +++ b/packages/create-apiops/bin/create-apiops-project.js @@ -8,8 +8,6 @@ import { spawnSync } from "node:child_process"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const templateDir = path.resolve(__dirname, "..", "template"); -const repoRoot = path.resolve(__dirname, "..", "..", ".."); -const canonicalOpenApiSnippetPath = path.join(repoRoot, "src", "snippets", "api-contract-example.yaml"); const DEFAULTS = { name: "my-api-project", @@ -91,11 +89,6 @@ function copyDir(src, dest) { } } -function copyFile(src, dest) { - fs.mkdirSync(path.dirname(dest), { recursive: true }); - fs.copyFileSync(src, dest); -} - function replaceInFile(filePath, replacements) { let content = fs.readFileSync(filePath, "utf8"); for (const [from, to] of Object.entries(replacements)) { @@ -180,7 +173,6 @@ async function main() { } copyDir(templateDir, targetDir); - copyFile(canonicalOpenApiSnippetPath, path.join(targetDir, "specs", "openapi", "api.yaml")); const replacements = { "__PROJECT_NAME__": projectName, @@ -209,7 +201,7 @@ async function main() { const canvasInit = runCommand( process.execPath, [ - "./node_modules/apiops-cycles-method-data/packages/create-apiops/bin/method-cli.js", + "./node_modules/apiops-cycles-method-data/bin/method-cli.js", "generate-canvases", "--preset", "new-api", "--style", apiStyle, diff --git a/packages/create-apiops/bin/method-cli.js b/packages/create-apiops/bin/method-cli.js index 0b9b052..c3f8033 100644 --- a/packages/create-apiops/bin/method-cli.js +++ b/packages/create-apiops/bin/method-cli.js @@ -3,8 +3,11 @@ import fs from "node:fs"; import path from "node:path"; import { spawnSync } from "node:child_process"; import readline from "node:readline/promises"; +import { fileURLToPath } from "node:url"; import * as methodEngine from "../../../src/lib/method-engine.js"; +const currentFilePath = fileURLToPath(import.meta.url); + const STATION_SCRIPT_HINTS = { "api-product-strategy": { resources: "npm run method:resources:strategy", @@ -721,7 +724,7 @@ function printGeneratedCanvasText(result) { } } -async function main() { +export async function main() { const { command, options } = parseArgs(process.argv.slice(2)); if (command === "help") { @@ -783,7 +786,9 @@ async function main() { fail(`Unknown command: ${command}`); } -main().catch((error) => { - console.error(error instanceof Error ? error.message : error); - process.exit(1); -}); +if (process.argv[1] && path.resolve(process.argv[1]) === currentFilePath) { + main().catch((error) => { + console.error(error instanceof Error ? error.message : error); + process.exit(1); + }); +} diff --git a/packages/create-apiops/template/package.json b/packages/create-apiops/template/package.json index 7898c3c..bf14834 100644 --- a/packages/create-apiops/template/package.json +++ b/packages/create-apiops/template/package.json @@ -24,14 +24,14 @@ }, "scripts": { "preinstall": "node ./scripts/check-node-version.js", - "method": "node ./node_modules/apiops-cycles-method-data/packages/create-apiops/bin/method-cli.js", - "method:start": "node ./node_modules/apiops-cycles-method-data/packages/create-apiops/bin/method-cli.js start --default-locale __LOCALE__", + "method": "node ./node_modules/apiops-cycles-method-data/bin/method-cli.js", + "method:start": "node ./node_modules/apiops-cycles-method-data/bin/method-cli.js start --default-locale __LOCALE__", "method:stations": "node ./scripts/get-core-stations.js", - "method:resources:strategy": "node ./node_modules/apiops-cycles-method-data/packages/create-apiops/bin/method-cli.js resources --station api-product-strategy --locale __LOCALE__", - "method:resources:architecture": "node ./node_modules/apiops-cycles-method-data/packages/create-apiops/bin/method-cli.js resources --station api-platform-architecture --locale __LOCALE__", - "method:resources:design": "node ./node_modules/apiops-cycles-method-data/packages/create-apiops/bin/method-cli.js resources --station api-design --locale __LOCALE__", - "method:resources:audit-station": "node ./node_modules/apiops-cycles-method-data/packages/create-apiops/bin/method-cli.js resources --station api-audit --locale __LOCALE__", - "method:canvases:new-api": "node ./node_modules/apiops-cycles-method-data/packages/create-apiops/bin/method-cli.js generate-canvases --preset new-api --style \"__API_STYLE__\" --locale __LOCALE__ --output ./specs/canvases", + "method:resources:strategy": "node ./node_modules/apiops-cycles-method-data/bin/method-cli.js resources --station api-product-strategy --locale __LOCALE__", + "method:resources:architecture": "node ./node_modules/apiops-cycles-method-data/bin/method-cli.js resources --station api-platform-architecture --locale __LOCALE__", + "method:resources:design": "node ./node_modules/apiops-cycles-method-data/bin/method-cli.js resources --station api-design --locale __LOCALE__", + "method:resources:audit-station": "node ./node_modules/apiops-cycles-method-data/bin/method-cli.js resources --station api-audit --locale __LOCALE__", + "method:canvases:new-api": "node ./node_modules/apiops-cycles-method-data/bin/method-cli.js generate-canvases --preset new-api --style \"__API_STYLE__\" --locale __LOCALE__ --output ./specs/canvases", "method:canvas": "node ./scripts/get-method-canvas-metadata.js", "method:canvas:mappings": "node ./scripts/list-canvas-mappings.js", "method:canvas:rest": "node ./scripts/get-method-canvas-metadata.js restCanvas __LOCALE__", diff --git a/packages/create-apiops/template/specs/openapi/api.yaml b/packages/create-apiops/template/specs/openapi/api.yaml new file mode 100644 index 0000000..bfb0ecd --- /dev/null +++ b/packages/create-apiops/template/specs/openapi/api.yaml @@ -0,0 +1,272 @@ +openapi: 3.0.3 +info: + title: Sample Catalog API + version: 1.0.0 + description: | + Starter example for a read-only APIOps Cycles API. + This example keeps the contract audit-friendly and easy to extend. +servers: + - url: /v1 + description: Versioned API base path +tags: + - name: catalog + description: Browse and search catalog items +paths: + /items: + get: + tags: [catalog] + summary: List catalog items + description: Returns a paginated list of public catalog items. + operationId: listItems + parameters: + - $ref: "#/components/parameters/searchTerm" + - $ref: "#/components/parameters/categoryId" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/pageSize" + responses: + "200": + description: Item list + content: + application/json: + schema: + $ref: "#/components/schemas/ItemListResponse" + examples: + default: + value: + data: + - itemId: item-123 + slug: blue-widget + name: Blue Widget + status: published + page: + number: 1 + size: 20 + totalItems: 1 + "400": + $ref: "#/components/responses/BadRequest" + "429": + $ref: "#/components/responses/TooManyRequests" + /items/{itemId}: + get: + tags: [catalog] + summary: Get item by id + description: Returns a single public catalog item by opaque identifier. + operationId: getItemById + parameters: + - $ref: "#/components/parameters/itemId" + responses: + "200": + description: Item details + content: + application/json: + schema: + $ref: "#/components/schemas/ItemDetail" + "400": + $ref: "#/components/responses/BadRequest" + "404": + $ref: "#/components/responses/NotFound" + /items/by-slug/{slug}: + get: + tags: [catalog] + summary: Get item by slug + description: Returns a single item by public slug. + operationId: getItemBySlug + parameters: + - $ref: "#/components/parameters/slug" + responses: + "200": + description: Item details + content: + application/json: + schema: + $ref: "#/components/schemas/ItemDetail" + "404": + $ref: "#/components/responses/NotFound" + /categories/{categoryId}/items: + get: + tags: [catalog] + summary: List items in category + description: Returns public items in a category. + operationId: listItemsByCategory + parameters: + - $ref: "#/components/parameters/categoryId" + responses: + "200": + description: Category item list + content: + application/json: + schema: + $ref: "#/components/schemas/ItemListResponse" + "404": + $ref: "#/components/responses/NotFound" +components: + parameters: + itemId: + name: itemId + in: path + required: true + schema: + type: string + pattern: "^[a-z0-9][a-z0-9-]{1,63}$" + example: item-123 + slug: + name: slug + in: path + required: true + schema: + type: string + pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$" + example: blue-widget + categoryId: + name: categoryId + in: path + required: true + schema: + type: string + pattern: "^[a-z0-9][a-z0-9-]{1,63}$" + example: home-goods + searchTerm: + name: searchTerm + in: query + required: false + schema: + type: string + minLength: 1 + example: widget + page: + name: page + in: query + required: false + schema: + type: integer + minimum: 1 + default: 1 + pageSize: + name: pageSize + in: query + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + responses: + BadRequest: + description: Validation failed + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + examples: + default: + value: + code: BAD_REQUEST + message: Invalid request + NotFound: + description: Resource not found + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + TooManyRequests: + description: Rate limit exceeded + headers: + Retry-After: + schema: + type: integer + description: Seconds until the next allowed request. + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + schemas: + ItemListResponse: + type: object + required: [data, page] + properties: + data: + type: array + items: + $ref: "#/components/schemas/ItemSummary" + page: + $ref: "#/components/schemas/Page" + ItemSummary: + type: object + required: [itemId, slug, name, status] + properties: + itemId: + type: string + slug: + type: string + name: + type: string + status: + type: string + enum: [published, hidden] + ItemDetail: + allOf: + - $ref: "#/components/schemas/ItemSummary" + - type: object + properties: + description: + type: string + categories: + type: array + items: + type: string + variants: + type: array + items: + $ref: "#/components/schemas/Variant" + Variant: + type: object + required: [variantId, sku, price, inventory] + properties: + variantId: + type: string + sku: + type: string + price: + $ref: "#/components/schemas/Price" + inventory: + $ref: "#/components/schemas/Inventory" + Price: + type: object + required: [amount, currency] + properties: + amount: + type: number + format: decimal + currency: + type: string + example: EUR + Inventory: + type: object + required: [available] + properties: + available: + type: integer + minimum: 0 + reserved: + type: integer + minimum: 0 + source: + type: string + Page: + type: object + required: [number, size, totalItems] + properties: + number: + type: integer + size: + type: integer + totalItems: + type: integer + ErrorResponse: + type: object + required: [code, message] + properties: + code: + type: string + message: + type: string diff --git a/scripts/check-package-contents.mjs b/scripts/check-package-contents.mjs index 5001201..62b7aa8 100644 --- a/scripts/check-package-contents.mjs +++ b/scripts/check-package-contents.mjs @@ -56,6 +56,7 @@ assertHasFiles( rootFiles, [ "AGENTS.md", + "bin/method-cli.js", "skills/new-api-guide/SKILL.md", "packages/create-apiops/bin/create-apiops-project.js", "packages/create-apiops/template/AGENTS.md", @@ -104,7 +105,8 @@ assertHasFiles( "bin/check-node-version.js", "template/AGENTS.md", "template/README.md", - "template/package.json" + "template/package.json", + "template/specs/openapi/api.yaml" ], "create-apiops" ); diff --git a/scripts/test-create-apiops-scaffold.mjs b/scripts/test-create-apiops-scaffold.mjs index 076bf68..dd116a8 100644 --- a/scripts/test-create-apiops-scaffold.mjs +++ b/scripts/test-create-apiops-scaffold.mjs @@ -8,6 +8,7 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const repoRoot = resolve(__dirname, ".."); const cliPath = resolve(repoRoot, "packages/create-apiops/bin/create-apiops-project.js"); +const installedMethodCliPath = join("node_modules", "apiops-cycles-method-data", "bin", "method-cli.js"); const npmCacheDir = resolve(repoRoot, ".npm-pack-cache"); function resolveNpmCommand() { @@ -152,7 +153,7 @@ try { const guidedStartOutput = execFileSync( process.execPath, [ - join(projectDir, "node_modules", "apiops-cycles-method-data", "packages", "create-apiops", "bin", "method-cli.js"), + join(projectDir, installedMethodCliPath), "start", "--locale", "en", "--answers", guidedAnswers, @@ -175,7 +176,7 @@ try { const interactiveResourcesOutput = execFileSync( process.execPath, [ - join(projectDir, "node_modules", "apiops-cycles-method-data", "packages", "create-apiops", "bin", "method-cli.js"), + join(projectDir, installedMethodCliPath), "resources", "--station", "api-product-strategy", "--locale", "en", diff --git a/scripts/test-method-cli.mjs b/scripts/test-method-cli.mjs index 32838b9..689ba7b 100644 --- a/scripts/test-method-cli.mjs +++ b/scripts/test-method-cli.mjs @@ -8,7 +8,7 @@ import * as methodEngine from "../src/lib/method-engine.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const repoRoot = resolve(__dirname, ".."); -const methodCliPath = resolve(repoRoot, "packages/create-apiops/bin/method-cli.js"); +const methodCliPath = resolve(repoRoot, "bin/method-cli.js"); const scaffoldCliPath = resolve(repoRoot, "packages/create-apiops/bin/create-apiops-project.js"); function assert(condition, message) { From f688d4f75336a6d68bf3952aa4c7659b8f5f08c0 Mon Sep 17 00:00:00 2001 From: Marjukka Niinioja Date: Tue, 7 Apr 2026 11:44:52 +0300 Subject: [PATCH 2/3] update method-data to 4.3.3 to fix create-apiops paths (1.1.2) --- packages/create-apiops/template/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/create-apiops/template/package.json b/packages/create-apiops/template/package.json index bf14834..f881a43 100644 --- a/packages/create-apiops/template/package.json +++ b/packages/create-apiops/template/package.json @@ -6,7 +6,7 @@ "node": ">=22" }, "dependencies": { - "apiops-cycles-method-data": "^3.4.2", + "apiops-cycles-method-data": "^3.4.3", "canvascreator": "^1.7.3" }, "devDependencies": { From 7f72f33713784493c9526ba29370b1e6d5283781 Mon Sep 17 00:00:00 2001 From: Marjukka Niinioja Date: Tue, 7 Apr 2026 11:49:11 +0300 Subject: [PATCH 3/3] 3.4.3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 91fafd5..e117c7c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "apiops-cycles-method-data", - "version": "3.4.2", + "version": "3.4.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "apiops-cycles-method-data", - "version": "3.4.2", + "version": "3.4.3", "license": "Apache-2.0", "workspaces": [ "packages/*" diff --git a/package.json b/package.json index 8f23bf1..b512a42 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "apiops-cycles-method-data", - "version": "3.4.2", + "version": "3.4.3", "description": "APIOps Cycles Method data and canvases", "license": "Apache-2.0", "type": "module",