Skip to content

Commit

Permalink
Feat/cli/upload config (#2506)
Browse files Browse the repository at this point in the history
* feat(cli): added new command for upload-config

* docs: changeset for the new command upload-config

* fix(cli): eslint issues

* fix(cli): better error handling in isAppRegistered

* fix(cli): to many input params for isAppREgistered in  upload-export-config

* fix(cli): throwing error if app is not registrered

* fix(cli): error message when app is deleted
  • Loading branch information
eikeland authored Oct 8, 2024
1 parent a0d1359 commit 54618d6
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 102 deletions.
8 changes: 7 additions & 1 deletion .changeset/rare-carrots-give.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,15 @@ Commands:
- `-t, --tag <tag>` - Tag to apply to the uploaded app
- `-e, --env <ci | fqa | tr | fprd>` - Environment to tag the app in
- `-s, --service <service>` - Custom app service
- `build-config` - Publish the app config to a build version
- `build-config` - Generate app config for an environment
- `-o, --output <output>` - Output file for the app config
- `-c, --config <config>` - Path to the app config file (for config generation)
- `-p, --publish` - Flag for upload the generated config
- `-v, --version<semver | current | latest | preview>` - Publish the app config to version
- `-e, --env <ci | fqa | tr | fprd>` - Environment to publish the app config to
- `-s, --service <service>` - Custom app service
- `upload-config` - Upload the app config to a build version
- `-c, --config <config>` - Path to the app config json file to upload
- `-p, --publish<semver | current | latest | preview>` - Publish the app config to the build version
- `-e, --env <ci | fqa | tr | fprd>` - Environment to publish the app config to
- `-s, --service <service>` - Custom app service
Expand Down
100 changes: 2 additions & 98 deletions packages/cli/src/bin/create-export-config.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,24 @@
import nodeFs from 'node:fs';
import { writeFile } from 'node:fs/promises';
import { dirname } from 'node:path';
import semverValid from 'semver/functions/valid.js';

import { chalk, formatPath } from './utils/format.js';
import { Spinner } from './utils/spinner.js';

import {
getEndpointUrl,
loadAppConfig,
loadPackage,
isAppRegistered,
requireToken,
publishAppConfig,
} from './utils/index.js';
import type { FusionEnv } from './utils/index.js';
import { loadAppConfig, loadPackage } from './utils/index.js';

import { ConfigExecuterEnv } from '../lib/utils/config.js';
import { resolveAppKey } from '../lib/app-package.js';
import { exit } from 'node:process';
import assert from 'node:assert';

export const createExportConfig = async (options: {
command?: ConfigExecuterEnv['command'];
configFile?: string;
publish?: string;
outputFile?: string;
env: FusionEnv;
service: string;
}) => {
const { command = 'build', outputFile, configFile, publish, env, service } = options;
const { command = 'build', outputFile, configFile } = options;

const spinner = Spinner.Global({ prefixText: chalk.dim('config') });

const pkg = await loadPackage();
const appKey = resolveAppKey(pkg.packageJson);

const appEnv: ConfigExecuterEnv = {
command,
Expand Down Expand Up @@ -63,86 +47,6 @@ export const createExportConfig = async (options: {
console.log(config);
}

if (publish) {
spinner.info('Preparing to publishing config');

/* Make sure version is valid */
const version = publish === 'current' ? pkg.packageJson.version : publish;
if (!version || (!semverValid(version) && !['latest', 'preview'].includes(version))) {
spinner.fail(
'🙅‍♂️',
'Can not publish config to invalid version',
chalk.redBright(version),
'',
);
exit(1);
}

/** make sure user has a valid token */
try {
spinner.info('Validating FUSION_TOKEN');

// make sure token exist
requireToken();

// call service discovery with token, will throw error if failed
await getEndpointUrl('apps', env, '');

spinner.succeed('Found valid FUSION_TOKEN');
} catch (e) {
const err = e as Error;
spinner.fail(chalk.bgRed(err.message));
exit(1);
}

try {
spinner.info('Verifying that App is registered');

const state = { endpoint: '' };
try {
state.endpoint = await getEndpointUrl(`apps/${appKey}`, env, service);
} catch (e) {
const err = e as Error;
throw new Error(
`Could not get endpoint from service discovery while verifying app. service-discovery status: ${err.message}`,
);
}

const exist = await isAppRegistered(state.endpoint);
assert(exist, `${appKey} is not registered`);
spinner.succeed(`${appKey} is registered`);
} catch (e) {
const err = e as Error;
spinner.fail('🙅‍♂️', chalk.bgRed(err.message));
throw err;
}

try {
spinner.info(`Publishing config to "${appKey}@${version}"`);

const state = { endpoint: '' };
try {
state.endpoint = await getEndpointUrl(
`apps/${appKey}/builds/${version}/config`,
env,
service,
);
} catch (e) {
const err = e as Error;
throw new Error(
`Could not get endpoint from service discovery while publishig config. service-discovery status: ${err.message}`,
);
}

await publishAppConfig(state.endpoint, appKey, config);
spinner.succeed('✅', 'Published config to version', chalk.yellowBright(version));
} catch (e) {
const err = e as Error;
spinner.fail('🙅‍♂️', chalk.bgRed(err.message));
exit(1);
}
}

return config;
};

Expand Down
56 changes: 53 additions & 3 deletions packages/cli/src/bin/main.app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { formatPath, chalk } from './utils/format.js';
import { createAppManifest, createBuildManifest } from './create-export-manifest.js';
import { bundleApplication } from './bundle-application.js';
import { createExportConfig } from './create-export-config.js';
import { uploadExportConfig } from './upload-export-config.js';
import { fileURLToPath } from 'node:url';
import { resolve, join } from 'node:path';

Expand Down Expand Up @@ -111,12 +112,13 @@ export default (program: Command) => {
.description('Generate config')
.option('-o, --output <string>', 'output file')
.option('-c, --config <string>', 'application config file')
.option('-p, --publish', 'Publish config to app api')
.option(
'-p, --publish <string>',
'-v, --version <string>',
`Publish app config to version [${chalk.yellowBright('(semver | current | latest | preview)')}]`,
'current',
)
.requiredOption(
.option(
'-e, --env, <ci | fqa | tr | fprd>',
'Fusion environment to build api urls from. used when publishing config.',
)
Expand All @@ -125,10 +127,58 @@ export default (program: Command) => {
'Define uri to custom app service. You can also define the env variable CUSTOM_APPAPI to be used on all publish commands. the --env parameter is ignored when set',
)
.action((opt) => {
if (opt.publish) {
opt.output = opt.output ?? 'app.config.json';

if (!opt.env) {
console.error(
chalk.redBright(
'Missing required option --env when publishing, see --help for usage',
),
);
return;
}

createExportConfig({
outputFile: opt.output,
configFile: opt.config,
}).then(() => {
uploadExportConfig({
configFile: opt.output,
version: opt.version,
env: opt.env,
service: opt.service,
});
});
return;
}

createExportConfig({
outputFile: opt.output,
configFile: opt.config,
publish: opt.publish,
});
});

app.command('upload-config')
.description('Upload config file to app api')
.requiredOption('-c, --config <string>', 'Generated application config json file')
.option(
'-v, --version <string>',
`Publish app config to version [${chalk.yellowBright('(semver | current | latest | preview)')}]`,
'current',
)
.requiredOption(
'-e, --env, <ci | fqa | tr | fprd>',
'Fusion environment to build api urls from. used when publishing config.',
)
.option(
'-s, --service, <string>',
'Define uri to custom app service. You can also define the env variable CUSTOM_APPAPI to be used on all publish commands. the --env parameter is ignored when set',
)
.action((opt) => {
uploadExportConfig({
configFile: opt.config,
version: opt.version,
env: opt.env,
service: opt.service,
});
Expand Down
121 changes: 121 additions & 0 deletions packages/cli/src/bin/upload-export-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { existsSync, readFileSync } from 'node:fs';
import semverValid from 'semver/functions/valid.js';

import { chalk } from './utils/format.js';
import { Spinner } from './utils/spinner.js';

import {
getEndpointUrl,
loadPackage,
isAppRegistered,
requireToken,
publishAppConfig,
} from './utils/index.js';
import type { FusionEnv } from './utils/index.js';

import { ConfigExecuterEnv } from '../lib/utils/config.js';
import { resolveAppKey } from '../lib/app-package.js';
import { exit } from 'node:process';
import assert from 'node:assert';

export const uploadExportConfig = async (options: {
command?: ConfigExecuterEnv['command'];
configFile: string;
version: string;
env: FusionEnv;
service: string;
}) => {
const { configFile, version: pubVersion, env, service } = options;

const spinner = Spinner.Global({ prefixText: chalk.dim('config') });

const pkg = await loadPackage();
const appKey = resolveAppKey(pkg.packageJson);

if (!existsSync(`${configFile}`)) {
throw new Error(`Config file ${configFile} does not exist`);
}

const config = JSON.parse(readFileSync(`${configFile}`, 'utf8'));

spinner.info('Preparing to publishing config');

/* Make sure version is valid */
const version = pubVersion === 'current' ? pkg.packageJson.version : pubVersion;
if (!version || (!semverValid(version) && !['latest', 'preview'].includes(version))) {
spinner.fail(
'🙅‍♂️',
'Can not publish config to invalid version',
chalk.redBright(version),
'',
);
exit(1);
}

/** make sure user has a valid token */
try {
spinner.info('Validating FUSION_TOKEN');

// make sure token exist
requireToken();

// call service discovery with token, will throw error if failed
await getEndpointUrl('apps', env, '');

spinner.succeed('Found valid FUSION_TOKEN');
} catch (e) {
const err = e as Error;
spinner.fail(chalk.bgRed(err.message));
exit(1);
}

try {
spinner.info('Verifying that App is registered');

const state = { endpoint: '' };
try {
state.endpoint = await getEndpointUrl(`apps/${appKey}`, env, service);
} catch (e) {
const err = e as Error;
throw new Error(
`Could not get endpoint from service discovery while verifying app. service-discovery status: ${err.message}`,
);
}

const exist = await isAppRegistered(state.endpoint);
assert(exist, `${appKey} is not registered`);

spinner.succeed(`${appKey} is registered`);
} catch (e) {
const err = e as Error;
spinner.fail('🙅‍♂️', chalk.bgRed(err.message));
throw err;
}

try {
spinner.info(`Publishing config to "${appKey}@${version}"`);

const state = { endpoint: '' };
try {
state.endpoint = await getEndpointUrl(
`apps/${appKey}/builds/${version}/config`,
env,
service,
);
} catch (e) {
const err = e as Error;
throw new Error(
`Could not get endpoint from service discovery while publishig config. service-discovery status: ${err.message}`,
);
}

await publishAppConfig(state.endpoint, appKey, config);
spinner.succeed('✅', 'Published config to version', chalk.yellowBright(version));
} catch (e) {
const err = e as Error;
spinner.fail('🙅‍♂️', chalk.bgRed(err.message));
exit(1);
}
};

export default uploadExportConfig;
4 changes: 4 additions & 0 deletions packages/cli/src/bin/utils/isAppRegistered.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export const isAppRegistered = async (endpoint: string): Promise<boolean> => {
return false;
}

if (requestApp.status === 410) {
throw Error('App is deleted.');
}

const data = await requestApp.json();
throw Error('Custom Fusion error, see cause.', { cause: data });
};

0 comments on commit 54618d6

Please sign in to comment.