deploy static method

Future<void> deploy(
  1. Client cloudApiClient,
  2. FileUploaderFactory fileUploaderFactory, {
  3. required CommandLogger logger,
  4. required String projectId,
  5. required String projectDir,
  6. required String projectConfigFilePath,
  7. required int concurrency,
  8. required bool dryRun,
  9. required bool showFiles,
})

Implementation

static Future<void> deploy(
  final Client cloudApiClient,
  final FileUploaderFactory fileUploaderFactory, {
  required final CommandLogger logger,
  required final String projectId,
  required final String projectDir,
  required final String projectConfigFilePath,
  required final int concurrency,
  required final bool dryRun,
  required final bool showFiles,
}) async {
  logger.init('Deploying Serverpod Cloud project "$projectId".');

  final projectDirectory = Directory(projectDir);

  final pubspecValidator = TenantProjectPubspec.fromProjectDir(
    projectDirectory,
  );

  final issues = pubspecValidator.projectDependencyIssues();
  if (issues.isNotEmpty) {
    throw FailureException(errors: issues);
  }

  final config = ScloudConfigIO.readFromFile(projectConfigFilePath);

  if (config != null && config.scripts.preDeploy.isNotEmpty) {
    await ScriptRunner.runScripts(
      config.scripts.preDeploy,
      projectDir,
      logger,
      scriptType: 'pre-deploy',
    );
  }

  final Directory rootDirectory;
  final Iterable<String> includedSubPaths;
  if (pubspecValidator.isWorkspaceResolved()) {
    (rootDirectory, includedSubPaths) =
        WorkspaceProject.prepareWorkspacePaths(projectDirectory);

    logger.list(
      title: 'Including workspace packages',
      includedSubPaths.where(
        (final path) => path != ScloudIgnore.scloudDirName,
      ),
      level: LogLevel.debug,
    );
  } else {
    rootDirectory = projectDirectory;
    includedSubPaths = const ['.'];
  }

  late final List<int> projectZip;
  final isZipped = await logger.progress(
    'Zipping project...',
    newParagraph: true,
    () async {
      try {
        projectZip = await ProjectZipper.zipProject(
          logger: logger,
          rootDirectory: rootDirectory,
          beneath: includedSubPaths,
          fileReadPoolSize: concurrency,
          showFiles: showFiles,
          fileContentModifier: (final relativePath, final contentReader) async {
            final isPubspec =
                relativePath.endsWith('pubspec.yaml') &&
                !relativePath.contains('.scloud/');
            if (isPubspec) {
              final content = await contentReader();
              return WorkspaceProject.stripDevDependenciesFromPubspecContent(
                content,
              );
            }
            return null;
          },
        );
        return true;
      } on ProjectZipperExceptions catch (e) {
        switch (e) {
          case ProjectDirectoryDoesNotExistException():
            logger.error('Project directory does not exist: ${e.path}');
            break;
          case EmptyProjectException():
            logger.error(
              'No files to upload.',
              hint:
                  'Ensure that the correct project directory is selected (either through the --project-dir flag or the current directory) and check '
                  'that `.gitignore` and `.scloudignore` does not filter out all project files.',
            );
            break;
          case DirectorySymLinkException():
            logger.error(
              'Serverpod Cloud does not support directory symlinks: `${e.path}`',
            );
            break;
          case NonResolvingSymlinkException():
            logger.error(
              'Serverpod Cloud does not support non-resolving symlinks: `${e.path}` => `${e.target}`',
            );
            break;
          case NullZipException():
            logger.error(
              'Unknown error occurred while zipping project, please try again.',
            );
            break;
        }
        return false;
      }
    },
  );

  if (!isZipped) throw ErrorExitException('Failed to zip project.');

  if (dryRun) {
    await logger.progress('Dry run, skipping upload.', () async {
      return true;
    });
  } else {
    final success = await logger.progress('Uploading project...', () async {
      late final String uploadDescription;
      try {
        uploadDescription = await cloudApiClient.deploy
            .createUploadDescription(projectId);
      } on Exception catch (e, stackTrace) {
        throw FailureException.nested(
          e,
          stackTrace,
          'Failed to fetch upload description',
        );
      }

      try {
        final fileUploader = fileUploaderFactory(uploadDescription);
        final ret = await fileUploader.upload(
          Stream.fromIterable([projectZip]),
          projectZip.length,
        );
        if (!ret) {
          logger.error('Failed to upload project, please try again.');
        }
        return ret;
      } on DioException catch (e) {
        throw FailureException(
          error:
              'Failed to upload project: ${_uploadDioExceptionFormatter(e)}',
        );
      } on Exception catch (e, stackTrace) {
        throw FailureException.nested(
          e,
          stackTrace,
          'Failed to upload project',
        );
      }
    });

    if (!success) {
      throw ErrorExitException('Failed to upload project.');
    }

    logger.success(
      'Project uploaded successfully!',
      trailingRocket: true,
      newParagraph: true,
    );
  }

  if (config != null && config.scripts.postDeploy.isNotEmpty) {
    await ScriptRunner.runScripts(
      config.scripts.postDeploy,
      projectDir,
      logger,
      scriptType: 'post-deploy',
    );
  }
}