Metadata Migrations script (#453)
* Create a package * wip * wip * wip * wip * Move migration script to invoices * Add migration script * Update SDK to fix getAll method * Add restoring migration script * Add migrations consts * Add changesets * cr fixes
This commit is contained in:
parent
f76d8fe8da
commit
5e903aed00
9 changed files with 235 additions and 1 deletions
5
.changeset/real-onions-move.md
Normal file
5
.changeset/real-onions-move.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"saleor-app-invoices": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Update app-sdk to 0.38.0
|
40
apps/invoices/scripts/migrations/migration-utils.ts
Normal file
40
apps/invoices/scripts/migrations/migration-utils.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||||
|
|
||||||
|
import { createClient } from "../../src/lib/graphql";
|
||||||
|
import { createSettingsManager } from "../../src/modules/app-configuration/metadata-manager";
|
||||||
|
import { SaleorCloudAPL } from "@saleor/app-sdk/APL";
|
||||||
|
|
||||||
|
export const getMetadataManagerForEnv = (apiUrl: string, appToken: string) => {
|
||||||
|
const client = createClient(apiUrl, async () => ({
|
||||||
|
token: appToken,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return createSettingsManager(client);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const safeParse = (json?: string) => {
|
||||||
|
if (!json) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(json);
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const verifyRequiredEnvs = () => {
|
||||||
|
const requiredEnvs = ["SALEOR_CLOUD_TOKEN", "SALEOR_CLOUD_RESOURCE_URL", "SECRET_KEY"];
|
||||||
|
|
||||||
|
if (!requiredEnvs.every((env) => process.env[env])) {
|
||||||
|
throw new Error(`Missing envs: ${requiredEnvs.join(" | ")}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchCloudAplEnvs = () => {
|
||||||
|
const saleorAPL = new SaleorCloudAPL({
|
||||||
|
token: process.env.SALEOR_CLOUD_TOKEN!,
|
||||||
|
resourceUrl: process.env.SALEOR_CLOUD_RESOURCE_URL!,
|
||||||
|
});
|
||||||
|
|
||||||
|
return saleorAPL.getAll();
|
||||||
|
};
|
4
apps/invoices/scripts/migrations/v1-to-v2/const.ts
Normal file
4
apps/invoices/scripts/migrations/v1-to-v2/const.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export const MigrationV1toV2Consts = {
|
||||||
|
appConfigV2metadataKey: "app-config-v2",
|
||||||
|
appConfigV1metadataKey: "app-config",
|
||||||
|
};
|
3
apps/invoices/scripts/migrations/v1-to-v2/readme.MD
Normal file
3
apps/invoices/scripts/migrations/v1-to-v2/readme.MD
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
Run `npx tsx run-report.ts` to print report (dry-run)
|
||||||
|
Run `npx tsx run-migration.ts` to migrate
|
||||||
|
Run `npx tsx restore-migration.ts` to restore migration (remove metadata v2)
|
|
@ -0,0 +1,45 @@
|
||||||
|
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||||
|
|
||||||
|
import * as dotenv from "dotenv";
|
||||||
|
import { fetchCloudAplEnvs, verifyRequiredEnvs } from "../migration-utils";
|
||||||
|
import { createClient } from "../../../src/lib/graphql";
|
||||||
|
import { RemoveMetadataDocument } from "../../../generated/graphql";
|
||||||
|
import { MigrationV1toV2Consts } from "./const";
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
const runMigration = async () => {
|
||||||
|
verifyRequiredEnvs();
|
||||||
|
|
||||||
|
const allEnvs = await fetchCloudAplEnvs();
|
||||||
|
|
||||||
|
const results = await Promise.all(
|
||||||
|
allEnvs.map((env) => {
|
||||||
|
const client = createClient(env.saleorApiUrl, async () => ({
|
||||||
|
token: env.token,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return client
|
||||||
|
.mutation(RemoveMetadataDocument, {
|
||||||
|
id: env.appId,
|
||||||
|
keys: [MigrationV1toV2Consts.appConfigV2metadataKey],
|
||||||
|
})
|
||||||
|
.toPromise()
|
||||||
|
.then((r) => {
|
||||||
|
if (r.error) {
|
||||||
|
console.error("❌ Error removing metadata", r.error.message);
|
||||||
|
throw r.error.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error("❌ Error removing metadata", e);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(results);
|
||||||
|
};
|
||||||
|
|
||||||
|
runMigration();
|
67
apps/invoices/scripts/migrations/v1-to-v2/run-migration.ts
Normal file
67
apps/invoices/scripts/migrations/v1-to-v2/run-migration.ts
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||||
|
|
||||||
|
import * as dotenv from "dotenv";
|
||||||
|
import {
|
||||||
|
fetchCloudAplEnvs,
|
||||||
|
getMetadataManagerForEnv,
|
||||||
|
safeParse,
|
||||||
|
verifyRequiredEnvs,
|
||||||
|
} from "../migration-utils";
|
||||||
|
import { ConfigV1ToV2Transformer } from "../../../src/modules/app-configuration/schema-v2/config-v1-to-v2-transformer";
|
||||||
|
import { AppConfigV2MetadataManager } from "../../../src/modules/app-configuration/schema-v2/app-config-v2-metadata-manager";
|
||||||
|
import { AppConfigV2 } from "../../../src/modules/app-configuration/schema-v2/app-config";
|
||||||
|
import { MigrationV1toV2Consts } from "./const";
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
const runMigration = async () => {
|
||||||
|
verifyRequiredEnvs();
|
||||||
|
|
||||||
|
const allEnvs = await fetchCloudAplEnvs();
|
||||||
|
|
||||||
|
const results = await Promise.all(
|
||||||
|
allEnvs.map((env) => {
|
||||||
|
const metadataManager = getMetadataManagerForEnv(env.saleorApiUrl, env.token);
|
||||||
|
|
||||||
|
return Promise.all([
|
||||||
|
metadataManager.get(MigrationV1toV2Consts.appConfigV1metadataKey, env.saleorApiUrl),
|
||||||
|
metadataManager.get(MigrationV1toV2Consts.appConfigV2metadataKey),
|
||||||
|
])
|
||||||
|
.then(([v1, v2]) => {
|
||||||
|
if (v2 && v2 !== "undefined") {
|
||||||
|
console.log("▶️ v2 already exists for ", env.saleorApiUrl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!v1) {
|
||||||
|
console.log("🚫 v1 does not exist for ", env.saleorApiUrl);
|
||||||
|
|
||||||
|
return new AppConfigV2MetadataManager(metadataManager)
|
||||||
|
.set(new AppConfigV2().serialize())
|
||||||
|
.then((r) => {
|
||||||
|
console.log(`✅ created empty config for ${env.saleorApiUrl}`);
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(
|
||||||
|
`🚫 failed to create empty config for ${env.saleorApiUrl}. Env may not exist.`,
|
||||||
|
e.message
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const v2Config = new ConfigV1ToV2Transformer().transform(JSON.parse(v1));
|
||||||
|
|
||||||
|
return new AppConfigV2MetadataManager(metadataManager)
|
||||||
|
.set(v2Config.serialize())
|
||||||
|
.then((r) => {
|
||||||
|
console.log(`✅ migrated ${env.saleorApiUrl}`);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error("🚫 Failed to migrate ", env.saleorApiUrl, e);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
runMigration();
|
61
apps/invoices/scripts/migrations/v1-to-v2/run-report.ts
Normal file
61
apps/invoices/scripts/migrations/v1-to-v2/run-report.ts
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||||
|
|
||||||
|
import * as dotenv from "dotenv";
|
||||||
|
import {
|
||||||
|
fetchCloudAplEnvs,
|
||||||
|
getMetadataManagerForEnv,
|
||||||
|
safeParse,
|
||||||
|
verifyRequiredEnvs,
|
||||||
|
} from "../migration-utils";
|
||||||
|
import { MigrationV1toV2Consts } from "./const";
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
const runReport = async () => {
|
||||||
|
verifyRequiredEnvs();
|
||||||
|
|
||||||
|
const allEnvs = await fetchCloudAplEnvs().catch((r) => {
|
||||||
|
console.error(r);
|
||||||
|
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
const results = await Promise.all(
|
||||||
|
allEnvs.map((env) => {
|
||||||
|
const metadataManager = getMetadataManagerForEnv(env.saleorApiUrl, env.token);
|
||||||
|
|
||||||
|
return Promise.all([
|
||||||
|
metadataManager.get(MigrationV1toV2Consts.appConfigV1metadataKey, env.saleorApiUrl),
|
||||||
|
metadataManager.get(MigrationV1toV2Consts.appConfigV2metadataKey),
|
||||||
|
])
|
||||||
|
.then(([v1, v2]) => {
|
||||||
|
return {
|
||||||
|
schemaV1: safeParse(v1),
|
||||||
|
schemaV2: safeParse(v2),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.then((metadata) => ({
|
||||||
|
metadata: metadata,
|
||||||
|
env: env.saleorApiUrl,
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const report = results.map((r: any) => ({
|
||||||
|
env: r.env,
|
||||||
|
hasV1: !!r.metadata.schemaV1,
|
||||||
|
hasV2: !!r.metadata.schemaV2,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const notMigratedCount = report.reduce((acc: number, curr: any) => {
|
||||||
|
if (!curr.hasV2) {
|
||||||
|
return acc + 1;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
console.table(report);
|
||||||
|
console.log(`Envs left to migrate: ${notMigratedCount}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
runReport();
|
|
@ -35,6 +35,16 @@ gql`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
gql`
|
||||||
|
mutation RemoveMetadata($id: ID!, $keys: [String!]!) {
|
||||||
|
deletePrivateMetadata(id: $id, keys: $keys) {
|
||||||
|
errors {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export type SimpleGraphqlClient = Pick<Client, "mutation" | "query">;
|
export type SimpleGraphqlClient = Pick<Client, "mutation" | "query">;
|
||||||
|
|
||||||
export async function fetchAllMetadata(client: SimpleGraphqlClient): Promise<MetadataEntry[]> {
|
export async function fetchAllMetadata(client: SimpleGraphqlClient): Promise<MetadataEntry[]> {
|
||||||
|
|
|
@ -5536,7 +5536,6 @@ packages:
|
||||||
uuid: 8.3.2
|
uuid: 8.3.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@saleor/app-sdk@0.38.0(next@13.3.0)(react-dom@18.2.0)(react@18.2.0):
|
/@saleor/app-sdk@0.38.0(next@13.3.0)(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-JS/E3YODFHc+1DI+PczbV8jB09nLwzdQcwNs681RlwvR3JUC892hdBYYRdBKG5lauAcr4IxKw1IbrsxJKngtWA==}
|
resolution: {integrity: sha512-JS/E3YODFHc+1DI+PczbV8jB09nLwzdQcwNs681RlwvR3JUC892hdBYYRdBKG5lauAcr4IxKw1IbrsxJKngtWA==}
|
||||||
|
|
Loading…
Reference in a new issue