Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Release

on:
push:
branches: [ alpha, beta ]
branches: [ alpha, beta, main ]

permissions:
id-token: write # Required for OIDC
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
@apimatic/cli
=============
apimatic is in beta.

The official CLI for APIMatic.

Expand Down
21 changes: 21 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default [
setTimeout: "readonly",
clearTimeout: "readonly",
URL: "readonly",
Blob: "readonly",
NodeJS: true,
},
},
Expand All @@ -25,6 +26,26 @@ export default [
...tseslint.configs.recommended.rules,
},
},
{
// Test files use Mocha's global TDD/BDD functions and chai assertion
// expressions (e.g. `expect(x).to.be.true`), which the base config would
// otherwise flag as undefined globals / unused expressions.
files: ["test/**/*.ts"],
languageOptions: {
globals: {
describe: "readonly",
it: "readonly",
before: "readonly",
beforeEach: "readonly",
after: "readonly",
afterEach: "readonly",
},
},
rules: {
"@typescript-eslint/no-unused-expressions": "off",
"@typescript-eslint/no-explicit-any": "off",
},
},
{
ignores: [
"lib",
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"format": "prettier --write \"src/**/*.{js,ts}\"",
"lint": "eslint \"src/**/*.{js,ts}\"",
"lint:fix": "eslint --fix \"src/**/*.{js,ts}\" --quiet",
"readme": "oclif readme",
"test": "tsx node_modules/mocha/bin/_mocha --forbid-only \"test/**/*.test.ts\" --timeout 99999"
},
"dependencies": {
Expand Down Expand Up @@ -161,8 +162,7 @@
"description": "Generate a Table of Contents (TOC) file for your API documentation portal."
}
},
"topicSeparator": " ",
"state": "beta"
"topicSeparator": " "
},
"lint-staged": {
"*.js": "eslint --cache --fix",
Expand Down
25 changes: 13 additions & 12 deletions release.config.cjs
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
// eslint-disable-next-line no-undef
module.exports = {
// Three-tier release flow: alpha → beta → main.
// - `main` publishes stable releases to the npm `latest` dist-tag.
// - `beta` publishes prereleases to the `beta` dist-tag.
// - `alpha` publishes prereleases to the `alpha` dist-tag.
// The existing `v1.1.0-beta.*` git notes carry channels ["beta", null], so
// `main` (default/`latest` channel) sees beta.19 as its last release and
// graduates it to 1.1.0, while `beta` keeps its own counter — no notes
// migration is needed. (Previously `beta` used `channel: false` to point
// `latest` at the beta because no stable channel existed; `main` now owns it.)
branches: [
"v3",
"main",
{
name: "alpha",
name: "beta",
prerelease: true
},
{
name: "beta",
prerelease: true,
// Publish beta releases to the npm `latest` dist-tag (the default channel)
// rather than a `beta` dist-tag, so `npm install @apimatic/cli` resolves to
// the current beta. The dist-tag is applied during `npm publish`, so this
// works with OIDC trusted publishing (no NPM_TOKEN needed).
// NOTE: existing beta tags' git notes were migrated to include the default
// channel so version continuity is preserved (beta.N keeps incrementing).
channel: false
name: "alpha",
prerelease: true
}
],
plugins: [
Expand Down
23 changes: 15 additions & 8 deletions src/application/portal/recipe/recipe-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ import { stringify } from "yaml";
import { SerializableRecipe, ContentStepConfig, EndpointStepConfig } from "../../../types/recipe/recipe.js";
import { DirectoryPath } from "../../../types/file/directoryPath.js";
import { FileService } from "../../../infrastructure/file-service.js";
import { Toc } from "../../../types/toc/toc.js";
import { Toc, TocGroup, TocGenerated, TocCustomPage } from "../../../types/toc/toc.js";
import { FilePath } from "../../../types/file/filePath.js";
import { FileName } from "../../../types/file/fileName.js";
import { BuildContext } from "../../../types/build-context.js";

export class PortalRecipeGenerator {
//TODO: Replace tocFileContent any type with concrete type.
private readonly fileService: FileService = new FileService();

public async createRecipe(
Expand Down Expand Up @@ -41,8 +40,10 @@ export class PortalRecipeGenerator {
recipeName: string,
recipeMarkdownFileName: FileName
): Promise<void> {
let toc = tocData.toc as any;
let apiRecipesGroup = toc.find((item: any) => item.group === "API Recipes");
const toc = tocData.toc;
const isGroup = (item: TocGroup | TocGenerated): item is TocGroup => "group" in item;

let apiRecipesGroup = toc.find((item): item is TocGroup => isGroup(item) && item.group === "API Recipes");

if (!apiRecipesGroup) {
// If the group doesn't exist, create and insert it after the last group
Expand All @@ -53,19 +54,25 @@ export class PortalRecipeGenerator {
// Insert after the last group section, before generate sections
let lastGroupIdx = -1;
for (let i = 0; i < toc.length; i++) {
if (toc[i].group) lastGroupIdx = i;
const item = toc[i];
// `&& item.group` preserves the original truthiness check (a group with
// an empty name is not treated as a section boundary).
if (isGroup(item) && item.group) lastGroupIdx = i;
}
toc.splice(lastGroupIdx + 1, 0, apiRecipesGroup);
}

const recipeFile = `recipes/${recipeMarkdownFileName}`;
// Only add the recipe if it doesn't already exist
const existingRecipe = apiRecipesGroup.items.find(
(item: any) => item.page === recipeName || item.file === `recipes/${recipeMarkdownFileName}`
(item): item is TocCustomPage => "page" in item && (item.page === recipeName || item.file === recipeFile)
);
if (!existingRecipe) {
apiRecipesGroup.items.push({
// `items` is declared readonly on the Toc model, but recipe registration
// intentionally appends a custom page before persisting the whole TOC.
(apiRecipesGroup.items as TocCustomPage[]).push({
page: recipeName,
file: `recipes/${recipeMarkdownFileName}`
file: recipeFile
});
await this.fileService.writeContents(tocFilePath, stringify(tocData));
}
Expand Down
4 changes: 2 additions & 2 deletions src/infrastructure/services/validation-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { FilePath } from "../../types/file/filePath.js";
import { CommandMetadata } from "../../types/common/command-metadata.js";
import FormData from "form-data";
import { handleServiceError, ServiceError } from "../service-error.js";
import axios from "axios";
import axios, { AxiosResponse } from "axios";
import { envInfo } from "../env-info.js";
import { Buffer } from "node:buffer";

Expand Down Expand Up @@ -158,7 +158,7 @@ export class ValidationService {
return "Unexpected error occurred while validating API specification.";
}

private async parseErrorResponse(response: any): Promise<ServiceError> {
private async parseErrorResponse(response: AxiosResponse): Promise<ServiceError> {
const chunks: Buffer[] = [];
for await (const chunk of response.data) {
chunks.push(Buffer.from(chunk));
Expand Down
Loading
Loading