@forge/lint

6.0.2-next.5-experimental-a1600ff6.0.2-next.3-experimental-fda9df6
out/lint/linters/server-side-linter/server-side-linter.js
+out/lint/linters/server-side-linter/server-side-linter.jsNew file
+209
Index: package/out/lint/linters/server-side-linter/server-side-linter.js
===================================================================
--- package/out/lint/linters/server-side-linter/server-side-linter.js
+++ package/out/lint/linters/server-side-linter/server-side-linter.js
@@ -0,0 +1,209 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.ServerSideLinter = exports.InvalidManifestError = exports.MissingManifestError = exports.NoManifestURLError = void 0;
+const tslib_1 = require("tslib");
+const manifest_1 = require("@forge/manifest");
+const linter_interface_1 = require("../../linter-interface");
+const abstract_linter_1 = require("../../abstract-linter");
+const cli_shared_1 = require("@forge/cli-shared");
+const tmp_1 = tslib_1.__importDefault(require("tmp"));
+const node_fs_1 = require("node:fs");
+class NoManifestURLError extends Error {
+    constructor() {
+        super(cli_shared_1.Text.artifact.error.noManifestUrl);
+    }
+}
+exports.NoManifestURLError = NoManifestURLError;
+class MissingManifestError extends cli_shared_1.UserError {
+    constructor() {
+        super(cli_shared_1.Text.lint.errorMissingManifest);
+    }
+}
+exports.MissingManifestError = MissingManifestError;
+class InvalidManifestError extends cli_shared_1.UserError {
+    constructor() {
+        super(cli_shared_1.Text.lint.errorInvalidManifest);
+    }
+}
+exports.InvalidManifestError = InvalidManifestError;
+class ZipArchiver {
+    archive;
+    tempFile = tmp_1.default.fileSync({ postfix: '.zip' });
+    resolves = [];
+    rejects = [];
+    constructor(archive) {
+        this.archive = archive;
+        const output = (0, node_fs_1.createWriteStream)(this.tempFile.name).on('close', this.onClose);
+        this.archive.pipe(output);
+        this.archive.on('warning', this.onArchiveError);
+        this.archive.on('error', this.onArchiveError);
+    }
+    static async create() {
+        const { ZipArchive } = await Promise.resolve().then(() => tslib_1.__importStar(require('archiver')));
+        return new ZipArchiver(new ZipArchive());
+    }
+    addFile(fileName, contents) {
+        this.archive.append(contents, { name: fileName });
+    }
+    finalise() {
+        return new Promise((resolve, reject) => {
+            this.resolves.push(resolve);
+            this.rejects.push(reject);
+            return this.archive.finalize();
+        });
+    }
+    onArchiveError = (err) => {
+        const error = new Error(err.message);
+        for (const reject of this.rejects) {
+            reject(error);
+        }
+    };
+    onClose = () => {
+        for (const resolve of this.resolves) {
+            resolve(this.tempFile.name);
+        }
+    };
+}
+class ManifestCheckGraphqlClient {
+    graphqlClient;
+    constructor(graphqlClient) {
+        this.graphqlClient = graphqlClient;
+    }
+    async createAppManifestUploadUrl(appId, environmentKey) {
+        const mutation = `
+      mutation forge_cli_createAppManifestUploadUrl($input: CreateAppManifestUploadUrlInput!) {
+        createAppManifestUploadUrl(input: $input) {
+          success
+          preSignedUrl
+          errors {
+            message
+            extensions {
+              errorType
+              statusCode
+            }
+          }
+        }
+      }`;
+        const { response: { createAppManifestUploadUrl: { success, preSignedUrl, errors } }, requestId } = await this.graphqlClient.mutate(mutation, {
+            input: {
+                appId,
+                environmentKey
+            }
+        });
+        if (!success) {
+            const error = (0, cli_shared_1.getError)(errors);
+            throw new cli_shared_1.GraphQlMutationError(`${error.message} (requestId: ${requestId || 'unknown'})`, {
+                requestId,
+                code: error.code,
+                statusCode: error.statusCode
+            });
+        }
+        if (!preSignedUrl) {
+            throw new NoManifestURLError();
+        }
+        return preSignedUrl;
+    }
+    async getAppPreDeploymentCheck(appId, environmentKey, manifestUrl, bypassRules) {
+        const query = `
+      query forge_cli_appPreDeploymentCheck($input: AppPreDeploymentCheckInput!) {
+        appPreDeploymentCheck(input: $input) {
+          outcome {
+            rule
+            category
+            value
+            reason
+            changes {
+              key
+              added
+              removed
+            }
+            errors {
+              key
+              message
+              line
+              column
+            }
+          }
+        }
+      }
+    `;
+        const result = await this.graphqlClient.query(query, {
+            input: {
+                appId,
+                environmentKey,
+                manifestUrl,
+                bypassRules
+            }
+        });
+        if (!result.appPreDeploymentCheck) {
+            throw new MissingManifestError();
+        }
+        if (!result.appPreDeploymentCheck.outcome) {
+            throw new InvalidManifestError();
+        }
+        return result.appPreDeploymentCheck.outcome.filter((p) => p !== null);
+    }
+}
+class ServerSideLinter extends abstract_linter_1.AbstractLinter {
+    environment;
+    manifest;
+    client;
+    fileReader;
+    archiver;
+    fileUploader;
+    approveRules;
+    manifestUploadUrl;
+    constructor(environment, manifest, client, fileReader, archiver, fileUploader, logger, approveRules) {
+        super(logger);
+        this.environment = environment;
+        this.manifest = manifest;
+        this.client = client;
+        this.fileReader = fileReader;
+        this.archiver = archiver;
+        this.fileUploader = fileUploader;
+        this.approveRules = approveRules;
+    }
+    static async instance(environment, manifest, graphqlClient, logger, approveRules) {
+        const fileSystemReader = new cli_shared_1.FileSystemReader();
+        return new ServerSideLinter(environment, manifest, new ManifestCheckGraphqlClient(graphqlClient), fileSystemReader, await ZipArchiver.create(), new cli_shared_1.HttpFileUploader(fileSystemReader), logger, approveRules);
+    }
+    mapServerSideResponse(results, fixer) {
+        const manifestLintResults = new linter_interface_1.LintResult(manifest_1.MANIFEST_FILE, fixer);
+        manifestLintResults.batchAdd(...(this.getResultsForFixer(results).map(({ rule, reason, category }) => {
+            return {
+                class: category.toLowerCase(),
+                message: reason,
+                reference: rule,
+                line: 0,
+                column: 0,
+                metadata: {}
+            };
+        }) ?? []).sort((a, b) => a.line - b.line));
+        return manifestLintResults;
+    }
+    async batchExecuteImpl() {
+        if (!this.manifestUploadUrl) {
+            throw new Error('Bootstrap failed');
+        }
+        const results = await this.client.getAppPreDeploymentCheck(this.manifest.app.id, this.environment, this.manifestUploadUrl, this.approveRules ? this.approveRules : []);
+        return [this.mapServerSideResponse(results, this.getFixer())];
+    }
+    async bootstrap() {
+        const manifestContent = this.fileReader.readBinaryFile(cli_shared_1.manifestFileName);
+        if (!manifestContent) {
+            throw new Error('No Manifest content found');
+        }
+        this.archiver.addFile(cli_shared_1.manifestFileName, manifestContent);
+        const archivePath = await this.archiver.finalise();
+        const uploadUrl = await this.client.createAppManifestUploadUrl(this.manifest.app.id, this.environment);
+        await this.fileUploader.uploadFromPath(uploadUrl, archivePath);
+        this.manifestUploadUrl = uploadUrl;
+    }
+    getFixer() {
+        return undefined;
+    }
+    getResultsForFixer(results) {
+        return results?.filter((outcome) => outcome.value) || [];
+    }
+}
+exports.ServerSideLinter = ServerSideLinter;