mirror of
https://github.com/podman-desktop/podman-desktop
synced 2026-04-21 09:37:22 +00:00
* refactor(mock): generate @podman-desktop/api mock from its definition instead of writing for each function and for each namespace the content reuse the .d.ts definition and wire automatically vi.fn() customize/override some classes implementation manually but most of it is generated fixes https://github.com/podman-desktop/podman-desktop/issues/14493 Signed-off-by: Florent Benoit <fbenoit@redhat.com>
125 lines
5.1 KiB
TypeScript
125 lines
5.1 KiB
TypeScript
/**********************************************************************
|
|
* Copyright (C) 2026 Red Hat, Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
***********************************************************************/
|
|
|
|
import path from 'node:path';
|
|
import fs from 'node:fs/promises';
|
|
import typescript from 'typescript';
|
|
import Mustache from 'mustache';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
type Namespaces = { [key: string]: string[] };
|
|
type Classes = { [key: string]: string[] };
|
|
|
|
async function extractNamespacesAndClassesFromAPI(
|
|
filePath: string,
|
|
): Promise<{ namespaces: Namespaces; classes: Classes }> {
|
|
const fileContent = await fs.readFile(filePath, 'utf-8');
|
|
const sourceFile = typescript.createSourceFile(filePath, fileContent, typescript.ScriptTarget.Latest, true);
|
|
|
|
const namespaces: Namespaces = {};
|
|
const classes: Classes = {};
|
|
|
|
const visit = (node: typescript.Node): void => {
|
|
if (typescript.isModuleDeclaration(node) && node.name.text) {
|
|
if (node.name.text === '@podman-desktop/api') {
|
|
typescript.forEachChild(node, visit);
|
|
return;
|
|
}
|
|
|
|
const namespaceName = node.name.text;
|
|
const functions: string[] = [];
|
|
|
|
if (node.body && typescript.isModuleBlock(node.body)) {
|
|
for (const statement of node.body.statements) {
|
|
if (typescript.isFunctionDeclaration(statement) && statement.name) {
|
|
functions.push(statement.name.text);
|
|
} else if (typescript.isVariableStatement(statement)) {
|
|
for (const declaration of statement.declarationList.declarations) {
|
|
if (typescript.isIdentifier(declaration.name)) {
|
|
functions.push(declaration.name.text);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (functions.length > 0) {
|
|
// Deduplicate to handle function overloads
|
|
namespaces[namespaceName] = Array.from(new Set(functions));
|
|
}
|
|
}
|
|
|
|
if (typescript.isClassDeclaration(node) && node.name) {
|
|
const modifiers = typescript.getModifiers(node);
|
|
const isExported = modifiers?.some(m => m.kind === typescript.SyntaxKind.ExportKeyword);
|
|
if (isExported) {
|
|
const className = node.name.text;
|
|
const methods: string[] = [];
|
|
for (const member of node.members) {
|
|
if (typescript.isMethodDeclaration(member) && member.name && typescript.isIdentifier(member.name)) {
|
|
methods.push(member.name.text);
|
|
}
|
|
}
|
|
// Deduplicate to handle method overloads
|
|
classes[className] = Array.from(new Set(methods));
|
|
}
|
|
}
|
|
|
|
typescript.forEachChild(node, visit);
|
|
};
|
|
|
|
visit(sourceFile);
|
|
return { namespaces, classes };
|
|
}
|
|
|
|
function toTemplateData(data: { namespaces: Namespaces; classes: Classes }) {
|
|
const namespaces = Object.entries(data.namespaces).map(([name, functions]) => ({ name, functions }));
|
|
const classes = Object.entries(data.classes).map(([name, methods]) => ({ name, methods }));
|
|
return { namespaces, classes };
|
|
}
|
|
|
|
export default async function setup(): Promise<void> {
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
const repoRoot = path.resolve(__dirname, '..');
|
|
const extensionApiTypePath = path.join(repoRoot, 'packages', 'extension-api', 'src', 'extension-api.d.ts');
|
|
const podmanDesktopApiMocksDir = path.join(repoRoot, '__mocks__', '@podman-desktop');
|
|
const apiGeneratedFile = path.join(podmanDesktopApiMocksDir, 'api.ts');
|
|
const templatePath = path.join(repoRoot, '__mocks__', 'api.mustache');
|
|
|
|
// skip if api.ts is already newer (from template or extension-api.d.ts file)
|
|
const extensionApiPathStats = await fs.stat(extensionApiTypePath);
|
|
const templatePathStats = await fs.stat(templatePath);
|
|
try {
|
|
const outputStats = await fs.stat(apiGeneratedFile);
|
|
const newestInputMtime = Math.max(extensionApiPathStats.mtimeMs, templatePathStats.mtimeMs);
|
|
if (outputStats.mtimeMs >= newestInputMtime) {
|
|
console.debug(' 🚀 __mocks__/@podman-desktop/api.ts up-to-date; skipping regeneration');
|
|
return;
|
|
}
|
|
} catch {
|
|
console.debug(' ⚙️ __mocks__/@podman-desktop/api.ts does not exist yet; generating it now…');
|
|
}
|
|
const data = await extractNamespacesAndClassesFromAPI(extensionApiTypePath);
|
|
const template = await fs.readFile(templatePath, 'utf-8');
|
|
const content = Mustache.render(template, toTemplateData(data));
|
|
|
|
await fs.mkdir(podmanDesktopApiMocksDir, { recursive: true });
|
|
await fs.writeFile(apiGeneratedFile, content, 'utf-8');
|
|
console.debug(' ✅ __mocks__/@podman-desktop/api.ts has been generated.');
|
|
}
|