alwaysai
Version:
The alwaysAI command-line interface (CLI)
155 lines (138 loc) • 4.46 kB
text/typescript
import { CliUsageError, CliTerseError } from '@alwaysai/alwayscli';
import * as crypto from 'crypto';
import { ALWAYSAI_CLI_EXECUTABLE_NAME } from '../../constants';
import { checkUserIsLoggedInComponent } from '../user';
import {
runWithSpinner,
JsSpawner,
tarFiles,
streamToBuffer
} from '../../util';
import { appCheckComponent } from './app-check-component';
import { appConfigureComponent } from './app-configure-component';
import { getCurrentProjectId, ProjectJsonFile } from '../../core/project';
import {
insertAppRecord,
getPresignedUrlAppUpload,
usePresignedUrlAppUpload
} from '../../infrastructure';
import { APP_IGNORE_FILES } from './app-install-component';
import { checkForInvalidModelsComponent } from './models/app-invalid-models-check-component';
type ApplicationData = {
filename: string;
tarBuffer: Buffer;
releaseManifest: string;
projectId: string;
releaseHash: string;
};
export async function appPublishComponent(props: {
yes: boolean;
name?: string;
excludes?: string[];
}) {
const { yes, name, excludes } = props;
await checkUserIsLoggedInComponent({ yes });
try {
await appCheckComponent({ ignoreTargetJsonFile: true });
} catch (err) {
if (yes) {
throw new CliUsageError(
`App is not properly configured. Did you run \`${ALWAYSAI_CLI_EXECUTABLE_NAME} app configure\`?`
);
} else {
await appConfigureComponent({ yes });
}
}
const invalidModels = await checkForInvalidModelsComponent();
if (Object.keys(invalidModels).length > 0) {
throw new CliTerseError(
`You do not have permission to use the following models, or the model version does not exist:\n\n` +
`${Object.entries(invalidModels)
.map(([model, version]) => `- ${model}: version ${version}`)
.join('\n')}\n\n` +
`Please remove these models before publishing the application again.`
);
}
const applicationPackage = await runWithSpinner(
createApplicationFiles,
[{ excludes: excludes || [] }],
'Create application package'
);
await runWithSpinner(
publishApplicationPackage,
[applicationPackage, name],
'Publish application package'
);
return applicationPackage.releaseHash;
}
async function createApplicationFiles(params: { excludes: string[] }) {
const { excludes } = params;
// Get project ID
const projectJsonFile = ProjectJsonFile().read();
const projectId = projectJsonFile.project
? projectJsonFile.project.id
: undefined;
if (projectId === undefined) {
throw new CliTerseError('Please set up a project for this app');
}
// Put src in tarball
const spawner = JsSpawner();
const ignore = APP_IGNORE_FILES.concat(['alwaysai.target.json'], excludes);
const tarfile = await tarFiles(spawner, ignore);
const tarBuffer = await streamToBuffer(tarfile);
// Generate release hash
const hashSum = crypto.createHash('sha256');
hashSum.update(tarBuffer);
const releaseHash = hashSum.digest('hex');
const releaseDate = new Date();
const filename = `${projectId}/${releaseHash}.tgz`;
// Generate release manifest
const releaseData = {
releaseHash,
releaseDate,
filename
};
const releaseManifest = JSON.stringify(releaseData, null, 2);
const applicationPackage: ApplicationData = {
filename,
tarBuffer,
releaseManifest,
projectId,
releaseHash
};
return applicationPackage;
}
async function publishApplicationPackage(
applicationPackage: ApplicationData,
name?: string
) {
const fileData = {
tarFileName: applicationPackage.filename,
tarFile: applicationPackage.tarBuffer,
releaseManifestName: `${applicationPackage.projectId}/release.json`,
releaseManifestFile: applicationPackage.releaseManifest
};
const presignedUrlTarFile = await getPresignedUrlAppUpload(
fileData.tarFileName,
'application/octet-stream'
);
const presignedUrlManifestFile = await getPresignedUrlAppUpload(
fileData.releaseManifestName,
'application/octet-stream'
);
await usePresignedUrlAppUpload(
presignedUrlTarFile.uploadURL,
fileData.tarFile
);
await usePresignedUrlAppUpload(
presignedUrlManifestFile.uploadURL,
fileData.releaseManifestFile
);
const record = {
hash: applicationPackage.releaseHash,
s3Path: applicationPackage.filename,
projectId: await getCurrentProjectId(),
name: name ?? ''
};
await insertAppRecord(record);
}