deploy static method
Future<void>
deploy(
- Client cloudApiClient,
- FileUploaderFactory fileUploaderFactory, {
- required CommandLogger logger,
- required String baseCommand,
- required String projectId,
- required String projectDir,
- required String projectConfigFilePath,
- required int concurrency,
- required bool wetRun,
- required bool showFiles,
- bool skipDartPubGet = false,
- bool skipTailingStatus = false,
- bool suppressCommandMessages = false,
- String? outputPath,
- String? dartVersionOverride,
- IOSink? stdout,
- IOSink? stderr,
Implementation
static Future<void> deploy(
Client cloudApiClient,
FileUploaderFactory fileUploaderFactory, {
required CommandLogger logger,
required String baseCommand,
required String projectId,
required String projectDir,
required String projectConfigFilePath,
required int concurrency,
required bool wetRun,
required bool showFiles,
bool skipDartPubGet = false,
bool skipTailingStatus = false,
bool suppressCommandMessages = false,
String? outputPath,
String? dartVersionOverride,
IOSink? stdout,
IOSink? stderr,
}) async {
if (!suppressCommandMessages) {
logger.init('Deploying Serverpod Cloud project "$projectId".');
}
final projectDirectory = Directory(projectDir);
final supportedSdkVersions = await fetchSupportedDartSdkVersions(
cloudApiClient,
);
final pubspecValidator = TenantProjectPubspec.fromProjectDir(
projectDirectory,
);
final pubspecIssues = pubspecValidator.projectDependencyIssues();
if (pubspecIssues.isNotEmpty) {
throw FailureException(errors: pubspecIssues);
}
final lockfileDirectory = TenantProject.resolveLockfileDirectory(
projectDirectory,
isWorkspaceResolved: pubspecValidator.isWorkspaceResolved(),
);
final config = ScloudConfigIO.readFromFile(projectConfigFilePath);
await _runDartPubGetIfNeeded(logger, skipDartPubGet, lockfileDirectory);
final lockfile = File(p.join(lockfileDirectory.path, 'pubspec.lock'));
final lockfileDartSdk = TenantProjectPubspec.readLockfileDartSdk(lockfile);
if (lockfileDartSdk.issues.isNotEmpty) {
throw FailureException(errors: lockfileDartSdk.issues);
}
final workspaceRootDir = pubspecValidator.isWorkspaceResolved()
? lockfileDirectory
: null;
final dartVersion = DartSdkSelector.selectDartSdkVersion(
supportedSdkMinorVersions: supportedSdkVersions,
commandLineVersion: dartVersionOverride,
scloudVersion: config?.dartSdk,
toolVersionsVersion: ToolVersionsIO.readDartVersionFromToolVersions(
<Directory>[projectDirectory, ?workspaceRootDir],
),
pubspecVersionConstraint: pubspecValidator.environmentSdkConstraint(),
lockVersionConstraint: lockfileDartSdk.constraint,
);
logger.debug('Selected Dart SDK $dartVersion.');
if (config != null && config.scripts.preDeploy.isNotEmpty) {
await ScriptRunner.runScripts(
config.scripts.preDeploy,
projectDir,
logger,
scriptType: 'pre-deploy',
padHeadingRight: StatusCommands.progressMessagePadLength,
stdout: stdout,
stderr: stderr,
);
}
await warnIfLegacyServerpodWithMultipleInstances(
cloudApiClient: cloudApiClient,
projectId: projectId,
logger: logger,
serverpodVersionConstraint: pubspecValidator.serverpodVersion,
);
final gitMetadata = await readGitMetadata(projectDir, logger: logger);
if (gitMetadata != null && gitMetadata.hasUncommittedChanges) {
logger.warning(
'You have uncommitted changes in your git repository.',
hint:
'These changes are included in the deployment, but the deploy '
'is recorded against the last commit. Commit your changes for '
'an accurate deployment history.',
newParagraph: true,
);
}
final projectFilePreparer = TenantProject.prepare(
projectDirectory,
tenantProjectPubspec: pubspecValidator,
);
if (projectFilePreparer.isWorkspace) {
logger.list(
projectFilePreparer.includedPackagePaths,
title: 'Including workspace packages',
level: LogLevel.debug,
newParagraph: true,
);
}
late final List<int> projectZip;
final isZipped = await logger.progress(
'Zipping project',
successMessage: 'Zipping successful.',
padRight: StatusCommands.progressMessagePadLength,
newParagraph: true,
() async {
try {
projectZip = await ProjectZipper.zipProject(
logger: logger,
rootDirectory: projectFilePreparer.rootDirectory,
beneath: projectFilePreparer.includedSubPaths,
fileReadPoolSize: concurrency,
showFiles: showFiles,
fileContentModifier: (relativePath, contentReader) async {
final pathSegments = p.split(relativePath);
if (pathSegments.contains('.scloud')) return null;
if (pathSegments.last == 'pubspec.yaml') {
final content = await contentReader();
return projectFilePreparer.filterPubspecYaml(content);
}
if (pathSegments.last == 'pubspec.lock') {
final content = await contentReader();
return projectFilePreparer.filterPubspecLock(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 (outputPath != null) {
await logger.progress(
'Writing zip file to $outputPath',
padRight: StatusCommands.progressMessagePadLength,
() async {
try {
final file = File(outputPath);
await file.writeAsBytes(projectZip);
return true;
} on Exception catch (e, stackTrace) {
throw FailureException.nested(
e,
stackTrace,
'Failed to write zip file to $outputPath',
);
}
},
);
}
if (wetRun) {
await logger.progress(
'Wet run, skipping deployment',
successMessage: 'Wet run, skipping deployment.',
padRight: StatusCommands.progressMessagePadLength,
() async {
return true;
},
);
if (config != null && config.scripts.postDeploy.isNotEmpty) {
await ScriptRunner.runScripts(
config.scripts.postDeploy,
projectDir,
logger,
scriptType: 'post-deploy',
padHeadingRight: StatusCommands.progressMessagePadLength,
stdout: stdout,
stderr: stderr,
);
}
logger.info(
'The project was not deployed. '
'Run the command again without --wet-run to deploy it.',
newParagraph: true,
);
return;
}
final uploadDescription = await _createUploadDescription(
logger,
cloudApiClient,
projectId,
pubspecValidator.serverpodVersion,
'$dartVersion.0',
gitMetadata,
);
await _uploadProject(
logger,
fileUploaderFactory,
uploadDescription,
projectZip,
);
if (config != null && config.scripts.postDeploy.isNotEmpty) {
await ScriptRunner.runScripts(
config.scripts.postDeploy,
projectDir,
logger,
scriptType: 'post-deploy',
padHeadingRight: StatusCommands.progressMessagePadLength,
stdout: stdout,
stderr: stderr,
);
}
if (skipTailingStatus) {
logger.terminalCommand(
'$baseCommand status deployment show',
message: 'To view the deployment status, run this command:',
newParagraph: true,
);
return;
}
final attemptId = resolveUploadIdFromUploadDescription(uploadDescription);
if (attemptId == null) {
throw FailureException(
error: 'Failed to get deployment status.',
hint:
'Run this command to see recent deployments: '
'$baseCommand status deployment list',
);
}
await StatusCommands.tailDeploymentStatus(
cloudApiClient,
logger: logger,
baseCommand: baseCommand,
cloudCapsuleId: projectId,
attemptId: attemptId,
skipUploadStage: true,
);
}