console/scripts/seed-app-deployments.mts
2026-01-21 11:20:33 -06:00

283 lines
7.7 KiB
TypeScript

/**
* Script for seeding app deployments into an existing target.
*
* Requirements:
* - Docker Compose is started (pnpm start)
* - FEATURE_FLAGS_APP_DEPLOYMENTS_ENABLED=1 in server .env
* - A published schema in the target
*
* Example:
* `TOKEN=<access_token> pnpm seed:app-deployments`
*
* Where <access_token> is a Target Access Token from the target's Settings page.
*/
const token = process.env.TOKEN || process.env.HIVE_TOKEN;
if (!token) {
console.error('Missing "TOKEN" environment variable.');
console.error('Usage: TOKEN=<access_token> pnpm seed:app-deployments');
process.exit(1);
}
const graphqlEndpoint = 'http://localhost:3001/graphql';
async function executeGraphQL<T>(query: string, variables: Record<string, unknown>): Promise<T> {
const response = await fetch(graphqlEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ query, variables }),
});
const result = (await response.json()) as { data?: T; errors?: Array<{ message: string }> };
if (result.errors?.length) {
throw new Error(`GraphQL Error: ${result.errors.map(e => e.message).join(', ')}`);
}
return result.data as T;
}
const CreateAppDeployment = /* GraphQL */ `
mutation CreateAppDeployment($input: CreateAppDeploymentInput!) {
createAppDeployment(input: $input) {
error {
message
}
ok {
createdAppDeployment {
id
name
version
status
}
}
}
}
`;
const AddDocumentsToAppDeployment = /* GraphQL */ `
mutation AddDocumentsToAppDeployment($input: AddDocumentsToAppDeploymentInput!) {
addDocumentsToAppDeployment(input: $input) {
error {
message
}
ok {
appDeployment {
id
name
version
status
}
}
}
}
`;
const ActivateAppDeployment = /* GraphQL */ `
mutation ActivateAppDeployment($input: ActivateAppDeploymentInput!) {
activateAppDeployment(input: $input) {
error {
message
}
ok {
activatedAppDeployment {
id
name
version
status
}
}
}
}
`;
const RetireAppDeployment = /* GraphQL */ `
mutation RetireAppDeployment($input: RetireAppDeploymentInput!) {
retireAppDeployment(input: $input) {
error {
message
}
ok {
retiredAppDeployment {
id
name
version
status
}
}
}
}
`;
// Sample GraphQL documents for app deployments
const sampleDocuments = [
{ hash: 'get-user-query', body: 'query GetUser($id: ID!) { user(id: $id) { id name email } }' },
{
hash: 'list-users-query',
body: 'query ListUsers($limit: Int) { users(limit: $limit) { id name } }',
},
{
hash: 'create-user-mutation',
body: 'mutation CreateUser($input: CreateUserInput!) { createUser(input: $input) { id name } }',
},
{
hash: 'update-user-mutation',
body: 'mutation UpdateUser($id: ID!, $input: UpdateUserInput!) { updateUser(id: $id, input: $input) { id name } }',
},
{ hash: 'delete-user-mutation', body: 'mutation DeleteUser($id: ID!) { deleteUser(id: $id) }' },
{
hash: 'get-products-query',
body: 'query GetProducts($category: String) { products(category: $category) { id name price } }',
},
{
hash: 'get-product-query',
body: 'query GetProduct($id: ID!) { product(id: $id) { id name price description } }',
},
{
hash: 'search-query',
body: 'query Search($term: String!) { search(term: $term) { __typename ... on User { id name } ... on Product { id name } } }',
},
];
// App deployment configurations to create
const appDeployments = [
{ name: 'web-app', versions: ['1.0.0', '1.1.0', '1.2.0', '2.0.0'] },
{ name: 'mobile-app', versions: ['3.0.0', '3.1.0', '3.2.0'] },
{ name: 'admin-dashboard', versions: ['1.0.0', '1.0.1'] },
{ name: 'cli-tool', versions: ['0.1.0', '0.2.0', '1.0.0'] },
];
async function createAppDeploymentWithDocuments(
appName: string,
appVersion: string,
documents: Array<{ hash: string; body: string }>,
shouldActivate: boolean,
shouldRetire: boolean = false,
) {
// Create the app deployment
const createResult = await executeGraphQL<{
createAppDeployment: {
error: { message: string } | null;
ok: {
createdAppDeployment: { id: string; name: string; version: string; status: string };
} | null;
};
}>(CreateAppDeployment, { input: { appName, appVersion } });
if (createResult.createAppDeployment.error) {
console.error(
` Failed to create ${appName}@${appVersion}:`,
createResult.createAppDeployment.error.message,
);
return null;
}
console.log(` Created ${appName}@${appVersion} (pending)`);
// Add documents
const addDocsResult = await executeGraphQL<{
addDocumentsToAppDeployment: {
error: { message: string } | null;
ok: { appDeployment: { id: string } } | null;
};
}>(AddDocumentsToAppDeployment, { input: { appName, appVersion, documents } });
if (addDocsResult.addDocumentsToAppDeployment.error) {
console.error(
` Failed to add documents to ${appName}@${appVersion}:`,
addDocsResult.addDocumentsToAppDeployment.error.message,
);
return null;
}
console.log(` Added ${documents.length} documents to ${appName}@${appVersion}`);
if (shouldActivate) {
const activateResult = await executeGraphQL<{
activateAppDeployment: {
error: { message: string } | null;
ok: { activatedAppDeployment: { id: string } } | null;
};
}>(ActivateAppDeployment, { input: { appName, appVersion } });
if (activateResult.activateAppDeployment.error) {
console.error(
` Failed to activate ${appName}@${appVersion}:`,
activateResult.activateAppDeployment.error.message,
);
return null;
}
console.log(` Activated ${appName}@${appVersion}`);
if (shouldRetire) {
const retireResult = await executeGraphQL<{
retireAppDeployment: {
error: { message: string } | null;
ok: { retiredAppDeployment: { id: string } } | null;
};
}>(RetireAppDeployment, { input: { appName, appVersion } });
if (retireResult.retireAppDeployment.error) {
console.error(
` Failed to retire ${appName}@${appVersion}:`,
retireResult.retireAppDeployment.error.message,
);
return null;
}
console.log(` Retired ${appName}@${appVersion}`);
}
}
return createResult.createAppDeployment.ok?.createdAppDeployment;
}
console.log(`
GraphQL endpoint: ${graphqlEndpoint}
`);
console.log('Creating app deployments...\n');
for (const app of appDeployments) {
console.log(`Creating deployments for ${app.name}:`);
for (let i = 0; i < app.versions.length; i++) {
const version = app.versions[i];
const isLatest = i === app.versions.length - 1;
const isOldest = i === 0;
// Select a subset of documents for variety
const docsToUse = sampleDocuments.slice(0, 3 + (i % 5));
// Activate all versions, retire old ones (except the latest)
const shouldActivate = true;
const shouldRetire = !isLatest && isOldest;
await createAppDeploymentWithDocuments(
app.name,
version,
docsToUse,
shouldActivate,
shouldRetire,
);
}
console.log('');
}
// Create one pending deployment (not activated)
console.log('Creating a pending deployment:');
await createAppDeploymentWithDocuments(
'beta-app',
'0.0.1-beta',
sampleDocuments.slice(0, 2),
false, // Don't activate
);
console.log('\n========================================');
console.log('App deployments seeded successfully!');
console.log('========================================\n');