feat(types): add declarations for versioned manifest files and their types
Contains information on how to increment the schema revision
DELETE
packages/appium/types/appium-manifest.ts
-import {CommonExtMetadata, ExtMetadata, ExtSchemaMetadata} from './extension-manifest';
-import {ExtensionType, DriverType, PluginType} from '@appium/types';
-
-export type InstallType = 'npm' | 'git' | 'local' | 'github';
-
-export interface InternalMetadata {
- /**
- * Package name of extension
- *
- * `name` from its `package.json`
- */
- pkgName: string;
- /**
- * Version of extension
- *
- * `version` from its `package.json`
- */
- version: string;
- /**
- * The method in which the user installed the extension (the `source` CLI arg)
- */
- installType: InstallType;
- /**
- * Whatever the user typed as the extension to install. May be derived from `package.json`
- */
- installSpec: string;
- /**
- * Maximum version of Appium that this extension is compatible with.
- *
- * If `undefined`, we'll try anyway.
- */
- appiumVersion?: string;
-}
-
-/**
- * Combination of external + internal extension data with `driverName`/`pluginName` removed (it becomes a key in an {@linkcode ExtRecord} object).
- * Part of `extensions.yaml`.
- */
-export type ExtManifest<ExtType extends ExtensionType> = Omit<
- ExtMetadata<ExtType>,
- ExtType extends DriverType ? 'driverName' : ExtType extends PluginType ? 'pluginName' : never
-> &
- InternalMetadata &
- CommonExtMetadata; // XXX: ExtMetadata should be a union with CommonMetadata. why is this needed?
-
-export type WithSchemaManifest = {
- schema: ExtSchemaMetadata;
-};
-
-/**
- * This is just a {@linkcode ExtManifest} except it _for sure_ has a `schema` prop.
- */
-export type ExtManifestWithSchema<ExtType extends ExtensionType> = ExtManifest<ExtType> &
- WithSchemaManifest;
-
-/**
- * Generic type for an object keyed by extension name, with values of type {@linkcode ExtData}
- */
-export type ExtRecord<ExtType extends ExtensionType> = Record<string, ExtManifest<ExtType>>;
-
-export type DriverRecord = ExtRecord<DriverType>;
-export type PluginRecord = ExtRecord<PluginType>;
-
-export type ExtName<ExtType extends ExtensionType> = keyof ExtRecord<ExtType>;
-
-/**
- * Represents an entire YAML manifest (`extensions.yaml`)
- */
-export interface ManifestData {
- drivers: DriverRecord;
- plugins: PluginRecord;
- schemaRev?: number;
-}

DELETE
packages/appium/types/extension-manifest.ts
-/**
- * These types describe information about external extensions and the contents of their `package.json` files
- */
-
-import type {SchemaObject} from 'ajv';
-import type {PackageJson, SetRequired} from 'type-fest';
-import {DriverType, ExtensionType, PluginType} from '@appium/types';
-
-/**
- * This is what is allowed in the `appium.schema` prop of an extension's `package.json`.
- */
-export type ExtSchemaMetadata = string | (SchemaObject & {[key: number]: never});
-
-/**
- * Manifest data shared by all extensions, as contained in `package.json`
- */
-export interface CommonExtMetadata {
- mainClass: string;
- scripts?: Record<string, string>;
- schema?: ExtSchemaMetadata;
-}
-
-/**
- * Driver-specific manifest data as contained in `package.json`
- */
-export interface DriverMetadata {
- automationName: string;
- platformNames: string[];
- driverName: string;
-}
-
-/**
- * Plugin-specific manifest data as stored in `package.json`
- */
-export interface PluginMetadata {
- pluginName: string;
-}
-
-/**
- * Generic type to refer to either {@linkcode DriverMetadata} or {@linkcode PluginMetadata}
- * Corresponds to the `appium` prop in an extension's `package.json`.
- */
-export type ExtMetadata<ExtType extends ExtensionType> = (ExtType extends DriverType
- ? DriverMetadata
- : ExtType extends PluginType
- ? PluginMetadata
- : never) &
- CommonExtMetadata;
-
-/**
- * A `package.json` containing extension metadata.
- * Must have the following properties:
- * - `name`: the name of the extension
- * - `version`: the version of the extension
- * - `appium`: the metadata for the extension
- * - `peerDependencies.appium`: the maximum compatible version of Appium
- */
-export type ExtPackageJson<ExtType extends ExtensionType> = SetRequired<
- PackageJson,
- 'name' | 'version'
-> & {
- appium: ExtMetadata<ExtType>;
- peerDependencies: {appium: string; [key: string]: string};
-};

MODIFY
packages/appium/types/index.ts
packages/appium/types/index.ts
import {ExtensionType, DriverType, DriverClass, PluginType, PluginClass} from '@appium/types';
-export * from './appium-manifest';
-export * from './extension-manifest';
+export * from './manifest';
export * from './cli';
/**

ADD
packages/appium/types/manifest/README.md
+# appium-manifest types
+
+This dir contains versioned type declarations for our manifest file (`extensions.yaml`).
+
+## About
+
+Originally, the manifest file was versioned; it had a `schemaRev` prop. Eventually, the requirement for this prop was dropped, and became optional. As of this writing, it is now required again.
+
+The idea was that if Appium found an `extensions.yaml` with a `schemaRev` lower than its "current" revision, it would "migrate" the file to the current revision. When we removed the requirement for the `schemaRev` prop, we removed this logic.
+
+As of _v3_ of the schema, we are reinstating versioning and migrations.
+
+## How To Add A New Version
+
+The manifest file format composed of types used elsewhere in our codebase. This is probably a design flaw, but the fact remains that the manifest file's format is strongly coupled to Appium's internal logic around extensions.
+
+For that reason, it is _non-trivial_ to reuse the types declared by older revisions to declare a new revision. Until there's a time when these types (data structures) are de-coupled from the schema, we will have to use the _copy-and-paste_ strategy to create a new version.
+
+1. The current version of the schema is defined in `constants.js` as `CURRENT_SCHEMA_REV`. **Increment** this value.
+2. **Copy** the `.ts` file in this directory corresponding to the (now old) current version of the schema into a new file named `v<new-version>.ts`. For example, if the current version is `3`, and you're creating `4`, then you would copy `v3.ts` to `v4.ts`.
+3. **Modify** your new file to do whatever it is you need to do with it. Do _not_ build types upon old versions of the schema.
+4. **Modify** `index.ts`:
+ 1. **Append** `import * as ManifestV<new-version> from './v<new-version>'`
+ 2. **Replace** `export * from './v<current-version>'` with `export * from './v<new-version>'`
+ 3. **Append** `ManifestV<new-version>` to the `export {ManifestV...}` line.
+ 4. **Append** a key/value pair to `ManifestDataVersions` with the new _numeric_ version number as the key and the value `ManifestV<new-version>.ManifestData`.
+5. Finally, add a new key/value pair to the `Migrations` object in `manifest-migration.js` to perform the migration from the (old) current version to the new version.
+
+-- @boneskull, Nov 8 2022

ADD
packages/appium/types/manifest/base.ts
+import {DriverType, ExtensionType, PluginType} from '@appium/types';
+import {SchemaObject} from 'ajv';
+import {PackageJson, SetRequired} from 'type-fest';
+
+/**
+ * One of the possible extension installation stratgies
+ */
+export type InstallType = 'npm' | 'git' | 'local' | 'github';
+
+export interface InternalMetadata {
+ /**
+ * Package name of extension
+ *
+ * `name` from its `package.json`
+ */
+ pkgName: string;
+ /**
+ * Version of extension
+ *
+ * `version` from its `package.json`
+ */
+ version: string;
+ /**
+ * The method in which the user installed the extension (the `source` CLI arg)
+ */
+ installType: InstallType;
+ /**
+ * Whatever the user typed as the extension to install. May be derived from `package.json`
+ */
+ installSpec: string;
+ /**
+ * Maximum version of Appium that this extension is compatible with.
+ *
+ * If `undefined`, we'll try anyway.
+ */
+ appiumVersion?: string;
+}
+
+/**
+ * Shape of the `appium.schema` property in an extension's `package.json` (if it exists)
+ */
+export type ExtSchemaMetadata = string | (SchemaObject & {[key: number]: never});
+
+/**
+ * Manifest data shared by all extensions, as contained in `package.json`
+ */
+export interface CommonExtMetadata {
+ /**
+ * The main class of the extension.
+ *
+ * The extension must export this class by name.
+ */
+ mainClass: string;
+
+ /**
+ * Lookup table of scripts to run via `appium <driver|plugin> run <script>` keyed by name.
+ */
+ scripts?: Record<string, string>;
+
+ /**
+ * Schema describing configuration options (and CLI args) for the extension.
+ *
+ * Can also just be a path (relative to the extension root) to an external JSON schema file.
+ */
+
+ schema?: ExtSchemaMetadata;
+}
+
+/**
+ * Driver-specific manifest data as stored in a driver's `package.json`
+ */
+export interface DriverMetadata {
+ /**
+ * Automation name of the driver
+ */
+ automationName: string;
+ /**
+ * Platforms the driver supports
+ */
+ platformNames: string[];
+ /**
+ * Short name of the driver (displayed in `appium list`, etc.)
+ */
+ driverName: string;
+}
+
+/**
+ * Plugin-specific manifest data as stored in a plugin's `package.json`
+ */
+export interface PluginMetadata {
+ /**
+ * Short name of the plugin (displayed in `appium list`, etc.)
+ */
+ pluginName: string;
+}
+
+/**
+ * Generic extension metadata as stored in the `appium` prop of an extension's `package.json`.
+ */
+export type ExtMetadata<ExtType extends ExtensionType> = (ExtType extends DriverType
+ ? DriverMetadata
+ : ExtType extends PluginType
+ ? PluginMetadata
+ : never) &
+ CommonExtMetadata;
+
+/**
+ * Combination of external + internal extension data with `driverName`/`pluginName` removed (it becomes a key in an {@linkcode ExtRecord} object).
+ * Part of `extensions.yaml`.
+ */
+export type ExtManifest<ExtType extends ExtensionType> = Omit<
+ ExtMetadata<ExtType>,
+ 'driverName' | 'pluginName'
+> &
+ InternalMetadata;
+
+/**
+ * Lookup of extension name to {@linkcode ExtManifest}.
+ * @see {ManifestData}
+ */
+export type ExtRecord<ExtType extends ExtensionType> = Record<string, ExtManifest<ExtType>>;
+
+/**
+ * The shape of the `extensions.yaml` file
+ */
+export interface ManifestData {
+ drivers: ExtRecord<DriverType>;
+ plugins: ExtRecord<PluginType>;
+ schemaRev?: number;
+}
+
+/**
+ * Like {@linkcode ExtManifest} but for-sure has a `schema` prop.
+ */
+export type ExtManifestWithSchema<ExtType extends ExtensionType> = SetRequired<
+ ExtManifest<ExtType>,
+ 'schema'
+>;
+
+/**
+ * The name of an installed extension, as it appears in `extensions.yaml`
+ * (as a property name under `drivers` or `plugins`)
+ */
+export type ExtName<ExtType extends ExtensionType> = keyof ExtRecord<ExtType>;
+
+/**
+ * A `package.json` containing extension metadata.
+ * Must have the following properties:
+ * - `name`: the name of the extension
+ * - `version`: the version of the extension
+ * - `appium`: the metadata for the extension
+ * - `peerDependencies.appium`: the maximum compatible version of Appium
+ */
+export type ExtPackageJson<ExtType extends ExtensionType> = SetRequired<
+ PackageJson,
+ 'name' | 'version'
+> & {
+ appium: ExtMetadata<ExtType>;
+ peerDependencies: {appium: string; [key: string]: string};
+};
+
+/**
+ * A transient format between installation and insertion of extension metadata into the manifest.
+ */
+export type ExtInstallReceipt<ExtType extends ExtensionType> = ExtMetadata<ExtType> &
+ InternalMetadata;

ADD
packages/appium/types/manifest/index.ts
+/**
+ * See `README.md` for information on how to add a new version of the schema.
+ * @module
+ */
+
+import * as ManifestV2 from './base';
+import * as ManifestV3 from './v3';
+
+// add `import * as ManifestV<new-version> from './v<new-version>';` above
+
+export * from './v3';
+// replace above line with `export * from './v<new-version>';`
+
+export {ManifestV2, ManifestV3};
+
+export interface ManifestDataVersions {
+ 2: ManifestV2.ManifestData;
+ 3: ManifestV3.ManifestData;
+}
+// append to this interface your new version of `ManifestData`
+
+/**
+ * One of the known versions of the `extensions.yaml` schema.
+ *
+ * @privateRemarks You probably don't need to edit this.
+ */
+export type AnyManifestDataVersion = ManifestDataVersions[keyof ManifestDataVersions];

ADD
packages/appium/types/manifest/v3.ts
+import {DriverType, ExtensionType, PluginType} from '@appium/types';
+import {SchemaObject} from 'ajv';
+import {PackageJson, SetRequired} from 'type-fest';
+
+/**
+ * One of the possible extension installation stratgies
+ */
+export type InstallType = 'npm' | 'git' | 'local' | 'github';
+
+export interface InternalMetadata {
+ /**
+ * Package name of extension
+ *
+ * `name` from its `package.json`
+ */
+ pkgName: string;
+ /**
+ * Version of extension
+ *
+ * `version` from its `package.json`
+ */
+ version: string;
+ /**
+ * The method in which the user installed the extension (the `source` CLI arg)
+ */
+ installType: InstallType;
+ /**
+ * Whatever the user typed as the extension to install. May be derived from `package.json`
+ */
+ installSpec: string;
+ /**
+ * Maximum version of Appium that this extension is compatible with.
+ *
+ * If `undefined`, we'll try anyway.
+ */
+ appiumVersion?: string;
+ /**
+ * Path to the extension's root directory
+ */
+ installPath: string;
+}
+
+/**
+ * Shape of the `appium.schema` property in an extension's `package.json` (if it exists)
+ */
+export type ExtSchemaMetadata = string | (SchemaObject & {[key: number]: never});
+
+/**
+ * Manifest data shared by all extensions, as contained in `package.json`
+ */
+export interface CommonExtMetadata {
+ /**
+ * The main class of the extension.
+ *
+ * The extension must export this class by name.
+ */
+ mainClass: string;
+
+ /**
+ * Lookup table of scripts to run via `appium <driver|plugin> run <script>` keyed by name.
+ */
+ scripts?: Record<string, string>;
+
+ /**
+ * Schema describing configuration options (and CLI args) for the extension.
+ *
+ * Can also just be a path (relative to the extension root) to an external JSON schema file.
+ */
+ schema?: ExtSchemaMetadata;
+}
+
+/**
+ * Driver-specific manifest data as stored in a driver's `package.json`
+ */
+export interface DriverMetadata {
+ /**
+ * Automation name of the driver
+ */
+ automationName: string;
+ /**
+ * Platforms the driver supports
+ */
+ platformNames: string[];
+ /**
+ * Short name of the driver (displayed in `appium list`, etc.)
+ */
+ driverName: string;
+}
+
+/**
+ * Plugin-specific manifest data as stored in a plugin's `package.json`
+ */
+export interface PluginMetadata {
+ /**
+ * Short name of the plugin (displayed in `appium list`, etc.)
+ */
+ pluginName: string;
+}
+
+/**
+ * Generic extension metadata as stored in the `appium` prop of an extension's `package.json`.
+ */
+export type ExtMetadata<ExtType extends ExtensionType> = (ExtType extends DriverType
+ ? DriverMetadata
+ : ExtType extends PluginType
+ ? PluginMetadata
+ : never) &
+ CommonExtMetadata;
+
+/**
+ * Combination of external + internal extension data with `driverName`/`pluginName` removed (it becomes a key in an {@linkcode ExtRecord} object).
+ * Part of `extensions.yaml`.
+ */
+export type ExtManifest<ExtType extends ExtensionType> = Omit<
+ ExtMetadata<ExtType>,
+ 'driverName' | 'pluginName'
+> &
+ InternalMetadata;
+
+/**
+ * Lookup of extension name to {@linkcode ExtManifest}.
+ * @see {ManifestData}
+ */
+export type ExtRecord<ExtType extends ExtensionType> = Record<string, ExtManifest<ExtType>>;
+
+/**
+ * The shape of the `extensions.yaml` file
+ */
+export interface ManifestData {
+ drivers: ExtRecord<DriverType>;
+ plugins: ExtRecord<PluginType>;
+ schemaRev: number;
+}
+
+/**
+ * Like {@linkcode ExtManifest} but for-sure has a `schema` prop.
+ */
+export type ExtManifestWithSchema<ExtType extends ExtensionType> = SetRequired<
+ ExtManifest<ExtType>,
+ 'schema'
+>;
+
+/**
+ * The name of an installed extension, as it appears in `extensions.yaml`
+ * (as a property name under `drivers` or `plugins`)
+ */
+export type ExtName<ExtType extends ExtensionType> = keyof ExtRecord<ExtType>;
+
+/**
+ * A `package.json` containing extension metadata.
+ * Must have the following properties:
+ * - `name`: the name of the extension
+ * - `version`: the version of the extension
+ * - `appium`: the metadata for the extension
+ * - `peerDependencies.appium`: the maximum compatible version of Appium
+ */
+export type ExtPackageJson<ExtType extends ExtensionType> = SetRequired<
+ PackageJson,
+ 'name' | 'version'
+> & {
+ appium: ExtMetadata<ExtType>;
+ peerDependencies: {appium: string; [key: string]: string};
+};
+
+/**
+ * A transient format between installation and insertion of extension metadata into the manifest.
+ */
+export type ExtInstallReceipt<ExtType extends ExtensionType> = ExtMetadata<ExtType> &
+ InternalMetadata;

feat(types): add declarations for versioned manifest files and their types

Contains information on how to increment the schema revision