diff --git a/packages/api/bin/buildVersionedFiles.ts b/packages/api/bin/buildVersionedFiles.ts new file mode 100644 index 00000000..0a6d4b61 --- /dev/null +++ b/packages/api/bin/buildVersionedFiles.ts @@ -0,0 +1,28 @@ +/** + * Updates packageInfo.ts and schema.json with the latest package info. + * This script is run every time a new release is tagged. + */ +import fs from 'node:fs'; + +// eslint-disable-next-line import/no-extraneous-dependencies, node/no-extraneous-import +import prettier from 'prettier'; + +import pkg from '../package.json' assert { type: 'json' }; +import { lockfileSchema } from '../src/lockfileSchema.js'; + +async function run() { + // use prettier to format schema file + const prettierConfig = await prettier.resolveConfig(process.cwd()); + const formattedSchema = await prettier.format(JSON.stringify(lockfileSchema), { parser: 'json', ...prettierConfig }); + fs.writeFileSync('./schema.json', formattedSchema); + + const packageInfo = ` +// This file is automatically updated by the build script. +export const PACKAGE_NAME = '${pkg.name}'; +export const PACKAGE_VERSION = '${pkg.version}'; +`.trimStart(); + + fs.writeFileSync('./src/packageInfo.ts', packageInfo); +} + +run(); diff --git a/packages/api/package.json b/packages/api/package.json index 2eae046d..979da0b3 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -9,13 +9,14 @@ "scripts": { "attw": "attw --pack --format table-flipped", "build": "tsc", + "build:versionedfiles": "NODE_OPTIONS=--no-warnings node --loader ts-node/esm bin/buildVersionedFiles.ts", "debug:bin": "NODE_OPTIONS=--no-warnings node --loader ts-node/esm src/bin.ts", "lint:types": "tsc --noEmit", - "prebuild": "rm -rf dist/ && npm run version", + "prebuild": "rm -rf dist/ && npm run build:versionedfiles", "prepack": "npm run build", "test": "vitest run --coverage", "test:smoke": "vitest --config=vitest-smoketest.config.ts", - "version": "node -p \"'// This file is automatically updated by the build script.\\nexport const PACKAGE_NAME = \\'' + require('./package.json').name + '\\';\\nexport const PACKAGE_VERSION = \\'' + require('./package.json').version + '\\';'\" > src/packageInfo.ts; git add src/packageInfo.ts" + "version": "npm run build:versionedfiles && git add schema.json src/packageInfo.ts" }, "repository": { "type": "git", diff --git a/packages/api/schema.json b/packages/api/schema.json index 4cfb8dac..85b3e292 100644 --- a/packages/api/schema.json +++ b/packages/api/schema.json @@ -6,16 +6,8 @@ "required": ["apis"], "additionalProperties": false, "properties": { - "$schema": { - "type": "string" - }, - "apis": { - "type": "array", - "description": "The list of installed APIs", - "items": { - "$ref": "#/definitions/api" - } - } + "$schema": { "type": "string" }, + "apis": { "type": "array", "description": "The list of installed APIs", "items": { "$ref": "#/definitions/api" } } }, "definitions": { "api": { @@ -70,7 +62,8 @@ "description": "The date that this SDK was last rebuilt or updated.", "examples": ["2023-10-19T20:35:39.268Z"] } - } + }, + "additionalProperties": false } } } diff --git a/packages/api/src/codegen/factory.ts b/packages/api/src/codegen/factory.ts index b51bfd34..24cad908 100644 --- a/packages/api/src/codegen/factory.ts +++ b/packages/api/src/codegen/factory.ts @@ -4,9 +4,14 @@ import type Oas from 'oas'; import TSGenerator from './languages/typescript/index.js'; -export enum SupportedLanguages { - JS = 'js', -} +export const SupportedLanguages = { + JS: 'js', +} as const; + +type ObjectValues = T[keyof T]; + +export type SupportedLanguage = ObjectValues; + export interface InstallerOptions { /** * Will initiate a dry run install process. Used for simulating installations within a unit test. @@ -21,7 +26,7 @@ export interface InstallerOptions { } export function codegenFactory( - language: SupportedLanguages, + language: SupportedLanguage, spec: Oas, specPath: string, identifier: string, @@ -36,7 +41,7 @@ export function codegenFactory( } export function uninstallerFactory( - language: SupportedLanguages, + language: SupportedLanguage, storage: Storage, opts: InstallerOptions = {}, ): Promise { diff --git a/packages/api/src/commands/install.ts b/packages/api/src/commands/install.ts index e6d26821..898a00a3 100644 --- a/packages/api/src/commands/install.ts +++ b/packages/api/src/commands/install.ts @@ -1,3 +1,5 @@ +import type { SupportedLanguage } from '../codegen/factory.js'; + import { Command, Option } from 'commander'; import figures from 'figures'; import Oas from 'oas'; @@ -21,10 +23,10 @@ cmd new Option('-l, --lang ', 'SDK language').default(SupportedLanguages.JS).choices([SupportedLanguages.JS]), ) .addOption(new Option('-y, --yes', 'Automatically answer "yes" to any prompts printed')) - .action(async (uri: string, options: { identifier?: string; lang: string; yes?: boolean }) => { - let language: SupportedLanguages; + .action(async (uri: string, options: { identifier?: string; lang: SupportedLanguage; yes?: boolean }) => { + let language: SupportedLanguage; if (options.lang) { - language = options.lang as SupportedLanguages; + language = options.lang; } else { ({ value: language } = await promptTerminal({ type: 'select', diff --git a/packages/api/src/lockfileSchema.ts b/packages/api/src/lockfileSchema.ts new file mode 100644 index 00000000..ec662c76 --- /dev/null +++ b/packages/api/src/lockfileSchema.ts @@ -0,0 +1,86 @@ +import type { FromSchema } from '@readme/api-core/lib'; + +import { SupportedLanguages } from './codegen/factory.js'; + +const lockfileApiSchema = { + type: 'object', + required: ['createdAt', 'identifier', 'installerVersion', 'integrity'], + properties: { + createdAt: { + type: 'string', + format: 'date-time', + description: 'The date that this SDK was installed.', + examples: ['2023-10-19T20:35:39.268Z'], + }, + identifier: { + type: 'string', + description: + "A unique identifier of the API. This'll be used to do imports on `@api/` and also where the SDK code will be located in `.api/apis/`", + examples: ['petstore'], + }, + installerVersion: { + type: 'string', + description: 'The version of `api` that was used to install this SDK.', + examples: ['7.0.0'], + }, + integrity: { + type: 'string', + description: + 'An integrity hash that will be used to determine on `npx api update` calls if the API has changed since the SDK was last generated.', + examples: ['sha512-otRF5TLMeDczSJlrmWLNDHLfmXg+C98oa/I/X2WWycwngh+a6WsbnjTbfwKGRU5DFbagOn2qX2SRvtBGOBRVGg=='], + }, + language: { + type: 'string', + description: 'The language that this SDK was generated for.', + default: SupportedLanguages.JS, + enum: Object.values(SupportedLanguages), + }, + private: { + type: 'boolean', + description: 'Was this SDK installed as a private, unpublished, package to the filesystem?', + }, + source: { + type: 'string', + description: 'The original source that was used to generate the SDK with.', + examples: [ + 'https://raw.githubusercontent.com/readmeio/oas-examples/main/3.0/json/petstore-simple.json', + './petstore.json', + '@developers/v2.0#nysezql0wwo236', + ], + }, + updatedAt: { + type: 'string', + format: 'date-time', + description: 'The date that this SDK was last rebuilt or updated.', + examples: ['2023-10-19T20:35:39.268Z'], + }, + }, + additionalProperties: false, +} as const; + +export const lockfileSchema = { + $schema: 'http://json-schema.org/draft-07/schema#', + title: 'api storage lockfile', + description: 'See https://api.readme.dev/docs', + type: 'object', + required: ['apis'], + additionalProperties: false, + properties: { + $schema: { + type: 'string', + }, + apis: { + type: 'array', + description: 'The list of installed APIs', + items: { + $ref: '#/definitions/api', + }, + }, + }, + definitions: { + api: lockfileApiSchema, + }, +} as const; + +export type Lockfile = FromSchema; +export type LockfileAPI = FromSchema; diff --git a/packages/api/src/storage.ts b/packages/api/src/storage.ts index 5b5d9155..fb400d2a 100644 --- a/packages/api/src/storage.ts +++ b/packages/api/src/storage.ts @@ -1,3 +1,5 @@ +import type { SupportedLanguage } from './codegen/factory.js'; +import type { Lockfile, LockfileAPI } from './lockfileSchema.js'; import type { OASDocument } from 'oas/rmoas.types'; import fs from 'node:fs'; @@ -31,7 +33,7 @@ export default class Storage { /** * The language that this SDK was generated for. */ - private language!: SupportedLanguages; + private language!: SupportedLanguage; /** * The identifier that this was installed as. @@ -40,7 +42,7 @@ export default class Storage { */ identifier!: string; - constructor(source: string, language?: SupportedLanguages, identifier?: string) { + constructor(source: string, language?: SupportedLanguage, identifier?: string) { Storage.setStorageDir(); this.fetcher = new Fetcher(source); @@ -168,7 +170,7 @@ export default class Storage { return res === undefined ? false : res; } - setLanguage(language?: SupportedLanguages) { + setLanguage(language?: SupportedLanguage) { // `language` wasn't always present in the lockfile so if we don't have one we should default // to JS. if (!language) { @@ -359,83 +361,3 @@ export default class Storage { fs.writeFileSync(Storage.getLockfilePath(), JSON.stringify(Storage.lockfile, null, 2)); } } - -/** - * @see schema.json - */ -export interface Lockfile { - /** @since 7.0.0 */ - $schema: string; - - /** - * The list of installed APIs. - */ - apis: LockfileAPI[]; -} - -/** - * @see schema.json - */ -export interface LockfileAPI { - /** - * The date that this SDK was installed. - * - * @since 7.0.0 - * @example 2023-10-19T20:35:39.268Z - */ - createdAt: string; - - /** - * A unique identifier of the API. This'll be used to do imports on `@api/` and also - * where the SDK code will be located in `.api/apis/`. - * - * @example petstore - */ - identifier: string; - - /** - * The version of `api` that was used to install this SDK. - * - * @example 5.0.0 - */ - installerVersion: string; - - /** - * An integrity hash that will be used to determine on `npx api update` calls if the API has - * changed since the SDK was last generated. - * - * @example sha512-ld+djZk8uRWmzXC+JYla1PTBScg0NjP/8x9vOOKRW+DuJ3NNMRjrpfbY7T77Jgnc87dZZsU49robbQfYe3ukug== - */ - integrity: string; - - /** - * The language that this SDK was generated for. - * - * @since 7.0.0 - */ - language: SupportedLanguages; - - /** - * Was this SDK installed as a private, unpublished, package to the filesystem? - * - * @since 7.0.0 - */ - private?: boolean; - - /** - * The original source that was used to generate the SDK with. - * - * @example https://raw.githubusercontent.com/readmeio/oas-examples/main/3.0/json/petstore-simple.json - * @example ./petstore.json - * @example @developers/v2.0#nysezql0wwo236 - */ - source: string; - - /** - * The date that this SDK was last rebuilt or updated. - * - * @since 7.0.0 - * @example 2023-10-19T20:35:39.268Z - */ - updatedAt?: string; -}