Skip to content

Commit

Permalink
feat(cli): enable staging for command with env variable
Browse files Browse the repository at this point in the history
  • Loading branch information
Plopix committed Jan 14, 2025
1 parent 1cf364a commit 9b39c4b
Show file tree
Hide file tree
Showing 13 changed files with 102 additions and 43 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@crystallize/cli",
"version": "5.0.0",
"version": "5.0.1",
"description": "Crystallize CLI",
"module": "src/index.ts",
"repository": "https://github.com/CrystallizeAPI/crystallize-cli",
Expand Down
9 changes: 6 additions & 3 deletions src/command/install-boilerplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { InstallBoilerplateStore } from '../core/journeys/install-boilerpla
import { Provider } from 'jotai';
import type { CredentialRetriever } from '../domain/contracts/credential-retriever';
import type { Logger } from '../domain/contracts/logger';
import type { QueryBus } from '../domain/contracts/bus';
import type { QueryBus, CommandBus } from '../domain/contracts/bus';

type Deps = {
logLevels: ('info' | 'debug')[];
Expand All @@ -17,6 +17,7 @@ type Deps = {
credentialsRetriever: CredentialRetriever;
logger: Logger;
queryBus: QueryBus;
commandBus: CommandBus;
};

export const createInstallBoilerplateCommand = ({
Expand All @@ -25,6 +26,7 @@ export const createInstallBoilerplateCommand = ({
installBoilerplateCommandStore,
credentialsRetriever,
queryBus,
commandBus,
logLevels,
}: Deps): Command => {
const command = new Command('install-boilerplate');
Expand All @@ -34,8 +36,7 @@ export const createInstallBoilerplateCommand = ({
command.addArgument(new Argument('[boilerplate-identifier]', 'The boilerplate identifier to use.'));
command.addOption(new Option('-b, --bootstrap-tenant', 'Bootstrap the tenant with initial data.'));

command.action(async (...args) => {
const [folder, tenantIdentifier, boilerplateIdentifier, flags] = args;
command.action(async (folder: string, tenantIdentifier: string, boilerplateIdentifier: string, flags) => {
logger.setBuffered(true);
await flySystem.createDirectoryOrFail(
folder,
Expand Down Expand Up @@ -67,6 +68,8 @@ export const createInstallBoilerplateCommand = ({
<Provider store={storage}>
<InstallBoilerplateJourney
queryBus={queryBus}
commandBus={commandBus}
logger={logger}
store={atoms}
credentialsRetriever={credentialsRetriever}
/>
Expand Down
23 changes: 14 additions & 9 deletions src/command/run-mass-operation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,23 @@ import type { Logger } from '../domain/contracts/logger';
import type { CommandBus } from '../domain/contracts/bus';
import type { Operation, Operations } from '@crystallize/schema/mass-operation';
import type { CredentialRetriever } from '../domain/contracts/credential-retriever';
import { createClient } from '@crystallize/js-api-client';
import pc from 'picocolors';
import { ZodError } from 'zod';
import type { createClient } from '@crystallize/js-api-client';

type Deps = {
logger: Logger;
commandBus: CommandBus;
credentialsRetriever: CredentialRetriever;
createCrystallizeClient: typeof createClient;
};

export const createRunMassOperationCommand = ({ logger, commandBus, credentialsRetriever }: Deps): Command => {
export const createRunMassOperationCommand = ({
logger,
commandBus,
credentialsRetriever,
createCrystallizeClient,
}: Deps): Command => {
const command = new Command('run-mass-operation');
command.description('Upload and start an Mass Operation Task in your tenant.');
command.addArgument(new Argument('<tenant-identifier>', 'The tenant identifier to use.'));
Expand All @@ -22,8 +28,7 @@ export const createRunMassOperationCommand = ({ logger, commandBus, credentialsR
command.option('--token_secret <token_secret>', 'Your access token secret.');
command.option('--legacy-spec', 'Use legacy spec format.');

command.action(async (...args) => {
const [tenantIdentifier, file, flags] = args;
command.action(async (tenantIdentifier: string, file: string, flags) => {
let operationsContent: Operations;
if (flags.legacySpec) {
logger.warn(`Using legacy spec... Converting to operations file...`);
Expand Down Expand Up @@ -76,17 +81,17 @@ export const createRunMassOperationCommand = ({ logger, commandBus, credentialsR
throw new Error('Task not started. Please check the logs for more information.');
}

const crystallizeClient = createClient({
const crystallizeClient = createCrystallizeClient({
tenantIdentifier,
accessTokenId: credentials.ACCESS_TOKEN_ID,
accessTokenSecret: credentials.ACCESS_TOKEN_SECRET,
});

logger.info(`Now, Waiting for task to complete...`);
while (startedTask.status !== 'complete') {
logger.info(`Task status: ${pc.yellow(startedTask.status)}`);
logger.info(`Now, Waiting for task ${pc.yellow(startedTask.id)} to complete...`);
while (startedTask?.status !== 'complete') {
logger.info(`Task status: ${pc.yellow(startedTask?.status)}`);
await new Promise((resolve) => setTimeout(resolve, 1000));
const get = await crystallizeClient.nextPimApi(getMassOperationBulkTask, { id: startedTask.id });
const get = await crystallizeClient.nextPimApi(getMassOperationBulkTask, { id: startedTask?.id });
if (get.bulkTask.error) {
throw new Error(get.data.bulkTask.error);
}
Expand Down
12 changes: 9 additions & 3 deletions src/core/create-credentials-retriever.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@ type Deps = {
options?: CredentialRetrieverOptions;
fallbackFile: string;
flySystem: FlySystem;
createCrystallizeClient: typeof createClient;
};
export const createCredentialsRetriever = ({ options, fallbackFile, flySystem }: Deps): CredentialRetriever => {
export const createCredentialsRetriever = ({
options,
fallbackFile,
flySystem,
createCrystallizeClient,
}: Deps): CredentialRetriever => {
const getCredentials = async (rOptions?: CredentialRetrieverOptions) => {
if (rOptions?.token_id && rOptions?.token_secret) {
return {
Expand Down Expand Up @@ -52,7 +58,7 @@ export const createCredentialsRetriever = ({ options, fallbackFile, flySystem }:
};

const checkCredentials = async (credentials: PimCredentials) => {
const apiClient = createClient({
const apiClient = createCrystallizeClient({
tenantIdentifier: '',
accessTokenId: credentials.ACCESS_TOKEN_ID,
accessTokenSecret: credentials.ACCESS_TOKEN_SECRET,
Expand All @@ -73,7 +79,7 @@ export const createCredentialsRetriever = ({ options, fallbackFile, flySystem }:
};

const fetchAvailableTenantIdentifier = async (credentials: PimCredentials, identifier: string) => {
const apiClient = createClient({
const apiClient = createCrystallizeClient({
tenantIdentifier: '',
accessTokenId: credentials.ACCESS_TOKEN_ID,
accessTokenSecret: credentials.ACCESS_TOKEN_SECRET,
Expand Down
15 changes: 15 additions & 0 deletions src/core/create-crystallize-client-builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { createClient, type ClientConfiguration, type CreateClientOptions } from '@crystallize/js-api-client';

type Deps = {
crystallizeEnvironment: 'staging' | 'production';
};
export const createCrystallizeClientBuilder =
({ crystallizeEnvironment }: Deps): typeof createClient =>
(configuration: ClientConfiguration, options?: CreateClientOptions) =>
createClient(
{
...configuration,
origin: crystallizeEnvironment === 'staging' ? '-dev.crystallize.digital' : '.crystallize.com',
},
options,
);
14 changes: 9 additions & 5 deletions src/core/di.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,24 @@ import { createS3Uploader } from './create-s3-uploader';
import os from 'os';
import { createRunMassOperationHandler } from '../domain/use-cases/run-mass-operation';
import { createRunMassOperationCommand } from '../command/run-mass-operation';
import type { createClient } from '@crystallize/js-api-client';
import { createCrystallizeClientBuilder } from './create-crystallize-client-builder';

const buildServices = () => {
export const buildServices = () => {
const logLevels = (
`${Bun.env.LOG_LEVELS}` === 'no-output' ? [] : ['info', ...`${Bun.env.LOG_LEVELS}`.split(',')]
) as ('info' | 'debug')[];
const crystallizeEnvironment = `${Bun.env.CRYSTALLIZE_ENVIRONMENT}` === 'staging' ? 'staging' : 'production';
const logger = createLogger('cli', logLevels);
const container = createContainer<{
logLevels: ('info' | 'debug')[];
crystallizeEnvironment: 'staging' | 'production';
logger: Logger;
queryBus: QueryBus;
commandBus: CommandBus;
flySystem: FlySystem;
credentialsRetriever: CredentialRetriever;
createCrystallizeClient: typeof createClient;
runner: ReturnType<typeof createRunner>;
s3Uploader: ReturnType<typeof createS3Uploader>;
// use cases
Expand All @@ -55,17 +60,18 @@ const buildServices = () => {
});
container.register({
logLevels: asValue(logLevels),
crystallizeEnvironment: asValue(crystallizeEnvironment),
logger: asValue(logger),
queryBus: asFunction(() => createQueryBus<QueryDefinitions>()).singleton(),
commandBus: asFunction(() => createCommandBus<CommandDefinitions>()).singleton(),
flySystem: asFunction(createFlySystem).singleton(),
credentialsRetriever: asFunction(createCredentialsRetriever)
.inject(() => ({
fallbackFile: `${os.homedir()}/.crystallize/credentials.json`,
fallbackFile: `${os.homedir()}/.crystallize/credentials${crystallizeEnvironment !== 'production' ? '-' + crystallizeEnvironment : ''}.json`,
options: undefined,
}))
.singleton(),

createCrystallizeClient: asFunction(createCrystallizeClientBuilder).singleton(),
runner: asFunction(createRunner).singleton(),
s3Uploader: asFunction(createS3Uploader).singleton(),

Expand Down Expand Up @@ -111,5 +117,3 @@ const buildServices = () => {
],
};
};
const services = buildServices();
export const { logger, createCommand, createQuery, dispatchCommand, dispatchQuery, commands } = services;
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,26 @@ import { Box, Text } from 'ink';
import { useEffect } from 'react';
import { colors } from '../../../styles';
import { Spinner } from '../../../../ui/components/spinner';
import { createQuery, dispatchQuery } from '../../../di';
import type { InstallBoilerplateStore } from '../create-store';
import { useAtom } from 'jotai';
import type { QueryBus } from '../../../../domain/contracts/bus';

type DownloadProjectProps = {
store: InstallBoilerplateStore['atoms'];
queryBus: QueryBus;
};
export const DownloadProject = ({ store }: DownloadProjectProps) => {
export const DownloadProject = ({ store, queryBus }: DownloadProjectProps) => {
const [state] = useAtom(store.stateAtom);
const [, boilerplateDownloaded] = useAtom(store.setDownloadedAtom);
const [isWizardFullfilled] = useAtom(store.isWizardFullfilledAtom);

useEffect(() => {
if (state.boilerplate) {
const query = createQuery('DownloadBoilerplateArchive', {
const query = queryBus.createQuery('DownloadBoilerplateArchive', {
boilerplate: state.boilerplate,
destination: state.folder!,
});
dispatchQuery(query).then(() => boilerplateDownloaded(true));
queryBus.dispatch(query).then(() => boilerplateDownloaded(true));
}
}, []);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { Box, Text } from 'ink';
import { useEffect, useState } from 'react';
import { colors } from '../../../styles';
import { Spinner } from '../../../../ui/components/spinner';
import { createCommand, dispatchCommand, logger } from '../../../di';
import type { InstallBoilerplateStore } from '../create-store';
import { useAtom } from 'jotai';
import type { CommandBus } from '../../../../domain/contracts/bus';
import type { Logger } from '../../../../domain/contracts/logger';

const feedbacks = [
'Fetching the dependencies...',
Expand All @@ -21,8 +22,10 @@ const feedbacks = [

type ExecuteRecipesProps = {
store: InstallBoilerplateStore['atoms'];
commandBus: CommandBus;
logger: Logger;
};
export const ExecuteRecipes = ({ store }: ExecuteRecipesProps) => {
export const ExecuteRecipes = ({ store, commandBus, logger }: ExecuteRecipesProps) => {
const [state] = useAtom(store.stateAtom);
const [isWizardFullfilled] = useAtom(store.isWizardFullfilledAtom);
const [, startImport] = useAtom(store.startBoostrappingAtom);
Expand All @@ -36,20 +39,20 @@ export const ExecuteRecipes = ({ store }: ExecuteRecipesProps) => {
}

(async () => {
const setupBoilerplateCommand = createCommand('SetupBoilerplateProject', {
const setupBoilerplateCommand = commandBus.createCommand('SetupBoilerplateProject', {
folder: state.folder!,
credentials: state.credentials,
tenant: state.tenant!,
});
const [setupResult, tenantResult] = await Promise.allSettled([
dispatchCommand(setupBoilerplateCommand),
commandBus.dispatch(setupBoilerplateCommand),
(async () => {
if (state.bootstrapTenant) {
const createTenantCommand = createCommand('CreateCleanTenant', {
const createTenantCommand = commandBus.createCommand('CreateCleanTenant', {
tenant: state.tenant!,
credentials: state.credentials!,
});
await dispatchCommand(createTenantCommand);
await commandBus.dispatch(createTenantCommand);
startImport();
}
})(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,22 @@ import { Success } from '../../../ui/components/success';
import type { InstallBoilerplateStore } from './create-store';
import { useAtom } from 'jotai';
import type { CredentialRetriever } from '../../../domain/contracts/credential-retriever';
import type { QueryBus } from '../../../domain/contracts/bus';
import type { CommandBus, QueryBus } from '../../../domain/contracts/bus';
import type { Logger } from '../../../domain/contracts/logger';

type InstallBoilerplateJourneyProps = {
store: InstallBoilerplateStore['atoms'];
credentialsRetriever: CredentialRetriever;
queryBus: QueryBus;
logger: Logger;
commandBus: CommandBus;
};
export const InstallBoilerplateJourney = ({
store,
credentialsRetriever,
queryBus,
commandBus,
logger,
}: InstallBoilerplateJourneyProps) => {
const [state] = useAtom(store.stateAtom);
const [, changeTenant] = useAtom(store.changeTenantAtom);
Expand Down Expand Up @@ -57,8 +62,8 @@ export const InstallBoilerplateJourney = ({
}}
/>
)}
{isWizardFullfilled && <DownloadProject store={store} />}
{state.isDownloaded && <ExecuteRecipes store={store} />}
{isWizardFullfilled && <DownloadProject store={store} queryBus={queryBus} />}
{state.isDownloaded && <ExecuteRecipes store={store} commandBus={commandBus} logger={logger} />}
<Messages messages={state.messages} />

{!isWizardFullfilled && <Tips fetchTips={fetchTips} />}
Expand Down
10 changes: 6 additions & 4 deletions src/domain/use-cases/create-clean-tenant.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { createClient } from '@crystallize/js-api-client';
import type { createClient } from '@crystallize/js-api-client';
import { jsonToGraphQLQuery } from 'json-to-graphql-query';
import type { Tenant } from '../contracts/models/tenant';
import type { CommandHandlerDefinition, Envelope } from 'missive.js';
import type { PimCredentials } from '../contracts/models/credentials';

type Deps = {};
type Deps = {
createCrystallizeClient: typeof createClient;
};
type Command = {
tenant: Tenant;
credentials: PimCredentials;
Expand All @@ -18,13 +20,13 @@ export type CreateCleanTenantHandlerDefinition = CommandHandlerDefinition<

const handler = async (
envelope: Envelope<Command>,
_: Deps,
{ createCrystallizeClient }: Deps,
): Promise<{
id: string;
identifier: string;
}> => {
const { tenant, credentials } = envelope.message;
const client = createClient({
const client = createCrystallizeClient({
tenantIdentifier: '',
accessTokenId: credentials.ACCESS_TOKEN_ID,
accessTokenSecret: credentials.ACCESS_TOKEN_SECRET,
Expand Down
1 change: 1 addition & 0 deletions src/domain/use-cases/fetch-tips.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type FetchTipsHandlerDefinition = QueryHandlerDefinition<

const handler = async (_: Envelope<Query>, deps: Deps) => {
const { logger } = deps;
// we call production all the time here on purpose.
const apiClient = createClient({
tenantIdentifier: 'crystallize_marketing',
});
Expand Down
13 changes: 11 additions & 2 deletions src/domain/use-cases/run-mass-operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { S3Uploader } from '../contracts/s3-uploader';
type Deps = {
logger: Logger;
s3Uploader: S3Uploader;
createCrystallizeClient: typeof createClient;
};

type Command = {
Expand All @@ -23,10 +24,18 @@ export type RunMassOperationHandlerDefinition = CommandHandlerDefinition<
Awaited<ReturnType<typeof handler>>
>;

const handler = async (envelope: Envelope<Command>, { logger, s3Uploader }: Deps) => {
const handler = async (
envelope: Envelope<Command>,
{ logger, s3Uploader, createCrystallizeClient }: Deps,
): Promise<{
task: {
id: string;
status: string;
};
}> => {
const { tenantIdentifier, operations: operationsContent } = envelope.message;

const crystallizeClient = createClient({
const crystallizeClient = createCrystallizeClient({
tenantIdentifier,
accessTokenId: envelope.message.credentials.ACCESS_TOKEN_ID,
accessTokenSecret: envelope.message.credentials.ACCESS_TOKEN_SECRET,
Expand Down
Loading

0 comments on commit 9b39c4b

Please sign in to comment.