diff --git a/CHANGELOG.md b/CHANGELOG.md index 66c9e99929..e185ef7818 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ This is the log of notable changes to EAS CLI and related packages. ### 🎉 New features - `eas update` now provides more information about the publish process including real-time feedback on asset uploads, update ids, and website links. ([#1152](https://github.com/expo/eas-cli/pull/1152) by [@kgc00](https://github.com/kgc00/)) +- Added first beta of `eas metadata` to sync store information using store configuration files ([#1136])(https://github.com/expo/eas-cli/pull/1136) by [@bycedric](https://github.com/bycedric)) ### 🐛 Bug fixes diff --git a/CODEOWNERS b/CODEOWNERS index f2c647ca67..f1b410761f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -4,4 +4,7 @@ packages/eas-cli/src/commands/build @dsokal @wkozyra95 packages/eas-cli/src/submit @barthap @dsokal packages/eas-cli/src/commands/submit.ts @barthap @dsokal +packages/eas-cli/src/metadata @bycedric +packages/eas-cli/src/commands/metadata @bycedric + packages/eas-json @dsokal @wkozyra95 diff --git a/packages/eas-cli/package.json b/packages/eas-cli/package.json index 9ba9091ecd..fec252948d 100644 --- a/packages/eas-cli/package.json +++ b/packages/eas-cli/package.json @@ -144,6 +144,9 @@ "device": { "description": "manage Apple devices for Internal Distribution" }, + "metadata": { + "description": "manage store configuration" + }, "project": { "description": "manage project" }, diff --git a/packages/eas-cli/src/commands/metadata/pull.ts b/packages/eas-cli/src/commands/metadata/pull.ts new file mode 100644 index 0000000000..ca0a056321 --- /dev/null +++ b/packages/eas-cli/src/commands/metadata/pull.ts @@ -0,0 +1,64 @@ +import { getConfig } from '@expo/config'; +import { Flags } from '@oclif/core'; +import chalk from 'chalk'; +import path from 'path'; + +import { ensureProjectConfiguredAsync } from '../../build/configure'; +import EasCommand from '../../commandUtils/EasCommand'; +import { CredentialsContext } from '../../credentials/context'; +import Log, { learnMore } from '../../log'; +import { createMetadataContextAsync } from '../../metadata/context'; +import { downloadMetadataAsync } from '../../metadata/download'; +import { handleMetadataError } from '../../metadata/errors'; +import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils'; +import { ensureLoggedInAsync } from '../../user/actions'; + +export default class MetadataPull extends EasCommand { + static description = 'generate the local store configuration from the app stores'; + + static flags = { + profile: Flags.string({ + description: + 'Name of the submit profile from eas.json. Defaults to "production" if defined in eas.json.', + }), + }; + + async runAsync(): Promise { + Log.warn('EAS Metadata is in beta and subject to breaking changes.'); + + const { flags } = await this.parse(MetadataPull); + const projectDir = await findProjectRootAsync(); + const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true }); + await getProjectIdAsync(exp); + await ensureProjectConfiguredAsync({ projectDir, nonInteractive: false }); + + const credentialsCtx = new CredentialsContext({ + exp, + projectDir, + user: await ensureLoggedInAsync(), + nonInteractive: false, + }); + + const metadataCtx = await createMetadataContextAsync({ + credentialsCtx, + projectDir, + exp, + profileName: flags.profile, + }); + + try { + const filePath = await downloadMetadataAsync(metadataCtx); + const relativePath = path.relative(process.cwd(), filePath); + + Log.addNewLineIfNone(); + Log.log(`🎉 Your store configuration is ready. + +- Update the ${chalk.bold(relativePath)} file to prepare the app information. +- Run ${chalk.bold('eas submit')} or manually upload a new app version to the app stores. +- Once the app is uploaded, run ${chalk.bold('eas metadata:push')} to sync the store configuration. +- ${learnMore('https://docs.expo.dev/eas-metadata/introduction/')}`); + } catch (error: any) { + handleMetadataError(error); + } + } +} diff --git a/packages/eas-cli/src/commands/metadata/push.ts b/packages/eas-cli/src/commands/metadata/push.ts new file mode 100644 index 0000000000..94adb5872c --- /dev/null +++ b/packages/eas-cli/src/commands/metadata/push.ts @@ -0,0 +1,55 @@ +import { getConfig } from '@expo/config'; +import { Flags } from '@oclif/core'; + +import { ensureProjectConfiguredAsync } from '../../build/configure'; +import EasCommand from '../../commandUtils/EasCommand'; +import { CredentialsContext } from '../../credentials/context'; +import Log from '../../log'; +import { createMetadataContextAsync } from '../../metadata/context'; +import { handleMetadataError } from '../../metadata/errors'; +import { uploadMetadataAsync } from '../../metadata/upload'; +import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils'; +import { ensureLoggedInAsync } from '../../user/actions'; + +export default class MetadataPush extends EasCommand { + static description = 'sync the local store configuration to the app stores'; + + static flags = { + profile: Flags.string({ + description: + 'Name of the submit profile from eas.json. Defaults to "production" if defined in eas.json.', + }), + }; + + async runAsync(): Promise { + Log.warn('EAS Metadata is in beta and subject to breaking changes.'); + + const { flags } = await this.parse(MetadataPush); + const projectDir = await findProjectRootAsync(); + const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true }); + await getProjectIdAsync(exp); + await ensureProjectConfiguredAsync({ projectDir, nonInteractive: false }); + + const credentialsCtx = new CredentialsContext({ + exp, + projectDir, + user: await ensureLoggedInAsync(), + nonInteractive: false, + }); + + const metadataCtx = await createMetadataContextAsync({ + credentialsCtx, + projectDir, + exp, + profileName: flags.profile, + }); + + try { + await uploadMetadataAsync(metadataCtx); + Log.addNewLineIfNone(); + Log.log(`🎉 Store configuration is synced with the app stores.`); + } catch (error: any) { + handleMetadataError(error); + } + } +} diff --git a/packages/eas-cli/src/metadata/apple/config/reader.ts b/packages/eas-cli/src/metadata/apple/config/reader.ts index 2677267304..07ee59b9d4 100644 --- a/packages/eas-cli/src/metadata/apple/config/reader.ts +++ b/packages/eas-cli/src/metadata/apple/config/reader.ts @@ -7,7 +7,7 @@ import { ReleaseType, } from '@expo/apple-utils'; -import { unique } from '../../utils/array'; +import uniq from '../../../utils/expodash/uniq'; import { AttributesOf } from '../../utils/asc'; import { removeDatePrecision } from '../../utils/date'; import { AppleMetadata } from '../types'; @@ -22,18 +22,18 @@ export const DEFAULT_WHATSNEW = 'Bug fixes and improved stability'; * This uses version 0 of the config schema. */ export class AppleConfigReader { - constructor(public readonly schema: AppleMetadata) {} + public constructor(public readonly schema: AppleMetadata) {} - getAgeRating(): Partial> | null { + public getAgeRating(): Partial> | null { return this.schema.advisory || null; } - getLocales(): string[] { + public getLocales(): string[] { // TODO: filter "default" locales, add option to add non-localized info to the config - return unique(Object.keys(this.schema.info || {})); + return uniq(Object.keys(this.schema.info || {})); } - getInfoLocale( + public getInfoLocale( locale: string ): PartialExcept, 'locale' | 'name'> | null { const info = this.schema.info?.[locale]; @@ -43,7 +43,7 @@ export class AppleConfigReader { return { locale, - name: info.title || 'no name provided', + name: info.title ?? 'no name provided', subtitle: info.subtitle, privacyChoicesUrl: info.privacyChoicesUrl, privacyPolicyText: info.privacyPolicyText, @@ -51,7 +51,7 @@ export class AppleConfigReader { }; } - getCategories(): CategoryIds | null { + public getCategories(): CategoryIds | null { if (Array.isArray(this.schema.categories) && this.schema.categories.length > 0) { return { primaryCategory: this.schema.categories[0], @@ -63,13 +63,13 @@ export class AppleConfigReader { } /** Get the `AppStoreVersion` object. */ - getVersion(): Partial< + public getVersion(): Partial< Omit, 'releaseType' | 'earliestReleaseDate'> > | null { return this.schema.copyright ? { copyright: this.schema.copyright } : null; } - getVersionRelease(): Partial< + public getVersionRelease(): Partial< Pick, 'releaseType' | 'earliestReleaseDate'> > | null { const { release } = this.schema; @@ -99,7 +99,7 @@ export class AppleConfigReader { return null; } - getVersionLocale( + public getVersionLocale( locale: string, context: { versionIsFirst: boolean } ): Partial> | null { diff --git a/packages/eas-cli/src/metadata/apple/config/writer.ts b/packages/eas-cli/src/metadata/apple/config/writer.ts index 95566af1e0..69e48411b6 100644 --- a/packages/eas-cli/src/metadata/apple/config/writer.ts +++ b/packages/eas-cli/src/metadata/apple/config/writer.ts @@ -18,18 +18,18 @@ export class AppleConfigWriter { constructor(public readonly schema: Partial = {}) {} /** Get the schema result to write it to the config file */ - toSchema(): { configVersion: number; apple: Partial } { + public toSchema(): { configVersion: number; apple: Partial } { return { configVersion: 0, apple: this.schema, }; } - setAgeRating(attributes: AttributesOf): void { + public setAgeRating(attributes: AttributesOf): void { this.schema.advisory = attributes; } - setInfoLocale(attributes: AttributesOf): void { + public setInfoLocale(attributes: AttributesOf): void { this.schema.info = this.schema.info ?? {}; const existing = this.schema.info[attributes.locale] ?? {}; @@ -43,7 +43,7 @@ export class AppleConfigWriter { }; } - setCategories({ primaryCategory, secondaryCategory }: AttributesOf): void { + public setCategories({ primaryCategory, secondaryCategory }: AttributesOf): void { this.schema.categories = []; // TODO: see why these types are conflicting @@ -55,13 +55,13 @@ export class AppleConfigWriter { } } - setVersion( + public setVersion( attributes: Omit, 'releaseType' | 'earliestReleaseDate'> ): void { this.schema.copyright = optional(attributes.copyright); } - setVersionRelease( + public setVersionRelease( attributes: Pick, 'releaseType' | 'earliestReleaseDate'> ): void { if (attributes.releaseType === ReleaseType.SCHEDULED) { @@ -83,7 +83,7 @@ export class AppleConfigWriter { } } - setVersionLocale(attributes: AttributesOf): void { + public setVersionLocale(attributes: AttributesOf): void { this.schema.info = this.schema.info ?? {}; const existing = this.schema.info[attributes.locale] ?? {}; diff --git a/packages/eas-cli/src/metadata/apple/task.ts b/packages/eas-cli/src/metadata/apple/task.ts index 037ab9cc55..9f7c1f0265 100644 --- a/packages/eas-cli/src/metadata/apple/task.ts +++ b/packages/eas-cli/src/metadata/apple/task.ts @@ -4,16 +4,16 @@ import { AppleData, PartialAppleData } from './data'; export abstract class AppleTask { /** Get a description from the task to use as section headings in the log */ - abstract name(): string; + public abstract name(): string; /** Prepare the data from the App Store to start syncing with the store configuration */ - abstract prepareAsync(options: TaskPrepareOptions): Promise; + public abstract prepareAsync(options: TaskPrepareOptions): Promise; /** Download all information from the App Store to generate the store configuration */ - abstract downloadAsync(options: TaskDownloadOptions): Promise; + public abstract downloadAsync(options: TaskDownloadOptions): Promise; /** Upload all information from the store configuration to the App Store */ - abstract uploadAsync(options: TaskUploadOptions): Promise; + public abstract uploadAsync(options: TaskUploadOptions): Promise; } export type TaskPrepareOptions = { diff --git a/packages/eas-cli/src/metadata/apple/tasks/age-rating.ts b/packages/eas-cli/src/metadata/apple/tasks/age-rating.ts index 6cb6319d4e..32fd755174 100644 --- a/packages/eas-cli/src/metadata/apple/tasks/age-rating.ts +++ b/packages/eas-cli/src/metadata/apple/tasks/age-rating.ts @@ -12,20 +12,20 @@ export type AgeRatingData = { }; export class AgeRatingTask extends AppleTask { - name = (): string => 'age rating declarations'; + public name = (): string => 'age rating declarations'; - async prepareAsync({ context }: TaskPrepareOptions): Promise { + public async prepareAsync({ context }: TaskPrepareOptions): Promise { assert(context.version, `App version information is not prepared, can't update age rating`); context.ageRating = (await context.version.getAgeRatingDeclarationAsync()) || undefined; } - async downloadAsync({ config, context }: TaskDownloadOptions): Promise { + public async downloadAsync({ config, context }: TaskDownloadOptions): Promise { if (context.ageRating) { config.setAgeRating(context.ageRating.attributes); } } - async uploadAsync({ config, context }: TaskUploadOptions): Promise { + public async uploadAsync({ config, context }: TaskUploadOptions): Promise { assert(context.ageRating, `Age rating not initialized, can't update age rating`); const ageRating = config.getAgeRating(); diff --git a/packages/eas-cli/src/metadata/apple/tasks/app-info.ts b/packages/eas-cli/src/metadata/apple/tasks/app-info.ts index aaa49d43e3..8544fbcd68 100644 --- a/packages/eas-cli/src/metadata/apple/tasks/app-info.ts +++ b/packages/eas-cli/src/metadata/apple/tasks/app-info.ts @@ -15,9 +15,9 @@ export type AppInfoData = { }; export class AppInfoTask extends AppleTask { - name = (): string => 'app information'; + public name = (): string => 'app information'; - async prepareAsync({ context }: TaskPrepareOptions): Promise { + public async prepareAsync({ context }: TaskPrepareOptions): Promise { const info = await retryIfNullAsync(() => context.app.getEditAppInfoAsync()); assert(info, 'Could not resolve the editable app info to update'); @@ -25,7 +25,7 @@ export class AppInfoTask extends AppleTask { context.infoLocales = await info.getLocalizationsAsync(); } - async downloadAsync({ config, context }: TaskDownloadOptions): Promise { + public async downloadAsync({ config, context }: TaskDownloadOptions): Promise { assert(context.info, `App info not initialized, can't download info`); config.setCategories(context.info.attributes); @@ -35,7 +35,7 @@ export class AppInfoTask extends AppleTask { } } - async uploadAsync({ config, context }: TaskUploadOptions): Promise { + public async uploadAsync({ config, context }: TaskUploadOptions): Promise { assert(context.info, `App info not initialized, can't update info`); const categories = config.getCategories(); @@ -68,14 +68,19 @@ export class AppInfoTask extends AppleTask { const model = context.infoLocales.find(model => model.attributes.locale === locale); await logAsync( - () => - model - ? model.updateAsync(attributes) - : context.info.createLocalizationAsync({ ...attributes, locale }), + async () => { + return model + ? await model.updateAsync(attributes) + : await context.info.createLocalizationAsync({ ...attributes, locale }); + }, { - pending: `${model ? 'Updating' : 'Creating'} localized info for ${locale}...`, - success: `${model ? 'Updated' : 'Created'} localized info for ${locale}`, - failure: `Failed ${model ? 'updating' : 'creating'} localized info for ${locale}`, + pending: `${model ? 'Updating' : 'Creating'} localized info for ${chalk.bold( + locale + )}...`, + success: `${model ? 'Updated' : 'Created'} localized info for ${chalk.bold(locale)}`, + failure: `Failed ${model ? 'updating' : 'creating'} localized info for ${chalk.bold( + locale + )}`, } ); } diff --git a/packages/eas-cli/src/metadata/apple/tasks/app-version.ts b/packages/eas-cli/src/metadata/apple/tasks/app-version.ts index f8e862eccc..0d062d553a 100644 --- a/packages/eas-cli/src/metadata/apple/tasks/app-version.ts +++ b/packages/eas-cli/src/metadata/apple/tasks/app-version.ts @@ -28,17 +28,17 @@ export type AppVersionData = { export class AppVersionTask extends AppleTask { private options: AppVersionOptions; - constructor(options: Partial = {}) { + public constructor(options: Partial = {}) { super(); this.options = { - platform: options.platform || Platform.IOS, - editLive: options.editLive || false, + platform: options.platform ?? Platform.IOS, + editLive: options.editLive ?? false, }; } - name = (): string => (this.options.editLive ? 'live app version' : 'editable app version'); + public name = (): string => (this.options.editLive ? 'live app version' : 'editable app version'); - async prepareAsync({ context }: TaskPrepareOptions): Promise { + public async prepareAsync({ context }: TaskPrepareOptions): Promise { const { version, versionIsFirst, versionIsLive } = await resolveVersionAsync( context.app, this.options @@ -52,7 +52,7 @@ export class AppVersionTask extends AppleTask { context.versionLocales = await version.getLocalizationsAsync(); } - async downloadAsync({ config, context }: TaskDownloadOptions): Promise { + public async downloadAsync({ config, context }: TaskDownloadOptions): Promise { assert(context.version, `App version not initialized, can't download version`); config.setVersion(context.version.attributes); @@ -63,7 +63,7 @@ export class AppVersionTask extends AppleTask { } } - async uploadAsync({ config, context }: TaskUploadOptions): Promise { + public async uploadAsync({ config, context }: TaskUploadOptions): Promise { assert(context.version, `App version not initialized, can't update version`); const version = config.getVersion(); @@ -71,16 +71,17 @@ export class AppVersionTask extends AppleTask { if (!version && !release) { Log.log(chalk`{dim - Skipped version and release update, not configured}`); } else { - const description = [version && 'version info', release && 'release info'] + const { versionString } = context.version.attributes; + const description = [version && 'version', release && 'release'] .filter(Boolean) .join(' and '); context.version = await logAsync( () => context.version.updateAsync({ ...version, ...release }), { - pending: `Updating ${description}...`, - success: `Updated ${description}`, - failure: `Failed updating ${description}`, + pending: `Updating ${description} info for ${chalk.bold(versionString)}...`, + success: `Updated ${description} info for ${chalk.bold(versionString)}...`, + failure: `Failed updating ${description} info for ${chalk.bold(versionString)}...`, } ); } @@ -97,14 +98,21 @@ export class AppVersionTask extends AppleTask { const oldModel = context.versionLocales.find(model => model.attributes.locale === locale); await logAsync( - () => - oldModel - ? oldModel.updateAsync(attributes) - : context.version.createLocalizationAsync({ ...attributes, locale }), + async () => { + return oldModel + ? await oldModel.updateAsync(attributes) + : await context.version.createLocalizationAsync({ ...attributes, locale }); + }, { - pending: `${oldModel ? 'Updating' : 'Creating'} localized version for ${locale}...`, - success: `${oldModel ? 'Updated' : 'Created'} localized version for ${locale}`, - failure: `Failed ${oldModel ? 'updating' : 'creating'} localized version for ${locale}`, + pending: `${oldModel ? 'Updating' : 'Creating'} localized version for ${chalk.bold( + locale + )}...`, + success: `${oldModel ? 'Updated' : 'Created'} localized version for ${chalk.bold( + locale + )}`, + failure: `Failed ${ + oldModel ? 'updating' : 'creating' + } localized version for ${chalk.bold(locale)}`, } ); } diff --git a/packages/eas-cli/src/metadata/context.ts b/packages/eas-cli/src/metadata/context.ts index 161d47c9c7..63a518de3a 100644 --- a/packages/eas-cli/src/metadata/context.ts +++ b/packages/eas-cli/src/metadata/context.ts @@ -64,7 +64,9 @@ export async function createMetadataContextAsync(params: { const exp = params.exp ?? getExpoConfig(params.projectDir); const user = await ensureLoggedInAsync(); - const bundleIdentifier = await getBundleIdentifierAsync(params.projectDir, exp); + const bundleIdentifier = + iosSubmissionProfile.bundleIdentifier ?? + (await getBundleIdentifierAsync(params.projectDir, exp)); return { platform: Platform.IOS, diff --git a/packages/eas-cli/src/metadata/download.ts b/packages/eas-cli/src/metadata/download.ts index 70e7441afe..f79ac1fc78 100644 --- a/packages/eas-cli/src/metadata/download.ts +++ b/packages/eas-cli/src/metadata/download.ts @@ -2,6 +2,7 @@ import fs from 'fs-extra'; import path from 'path'; import { MetadataEvent } from '../analytics/events'; +import Log from '../log'; import { confirmAsync } from '../prompts'; import { AppleData } from './apple/data'; import { createAppleTasks } from './apple/tasks'; @@ -33,6 +34,9 @@ export async function downloadMetadataAsync(metadataCtx: MetadataContext): Promi { app, auth } ); + Log.addNewLineIfNone(); + Log.log('Downloading App Store configuration...'); + const errors: Error[] = []; const config = createAppleWriter(); const tasks = createAppleTasks(metadataCtx); @@ -54,8 +58,11 @@ export async function downloadMetadataAsync(metadataCtx: MetadataContext): Promi } } - await fs.writeJson(filePath, config.toSchema(), { spaces: 2 }); - unsubscribeTelemetry(); + try { + await fs.writeJson(filePath, config.toSchema(), { spaces: 2 }); + } finally { + unsubscribeTelemetry(); + } if (errors.length > 0) { throw new MetadataDownloadError(errors, executionId); diff --git a/packages/eas-cli/src/metadata/errors.ts b/packages/eas-cli/src/metadata/errors.ts index 6142d96b2f..4bf3b63fa8 100644 --- a/packages/eas-cli/src/metadata/errors.ts +++ b/packages/eas-cli/src/metadata/errors.ts @@ -8,7 +8,7 @@ import Log, { link } from '../log'; * and should contain useful information for the user to solve before trying again. */ export class MetadataValidationError extends Error { - constructor(message?: string, public readonly errors?: ErrorObject[]) { + public constructor(message?: string, public readonly errors: ErrorObject[] = []) { super(message ?? 'Store configuration validation failed'); } } @@ -20,7 +20,7 @@ export class MetadataValidationError extends Error { * It contains that list of encountered errors to present to the user. */ export class MetadataUploadError extends Error { - constructor(public readonly errors: Error[], public readonly executionId: string) { + public constructor(public readonly errors: Error[], public readonly executionId: string) { super( `Store configuration upload encountered ${ errors.length === 1 ? 'an error' : `${errors.length} errors` @@ -36,7 +36,7 @@ export class MetadataUploadError extends Error { * It contains that list of encountered errors to present to the user. */ export class MetadataDownloadError extends Error { - constructor(public readonly errors: Error[], public readonly executionId: string) { + public constructor(public readonly errors: Error[], public readonly executionId: string) { super( `Store configuration download encountered ${ errors.length === 1 ? 'an error' : `${errors.length} errors` @@ -52,7 +52,9 @@ export class MetadataDownloadError extends Error { export function handleMetadataError(error: Error): void { if (error instanceof MetadataValidationError) { Log.error(error.message); - Log.log(error.errors?.map(err => ` - ${err.dataPath} ${err.message}`).join('\n')); + if (error.errors?.length > 0) { + Log.log(error.errors.map(err => ` - ${err.dataPath} ${err.message}`).join('\n')); + } return; } diff --git a/packages/eas-cli/src/metadata/upload.ts b/packages/eas-cli/src/metadata/upload.ts index 68c25c91c1..6aec641187 100644 --- a/packages/eas-cli/src/metadata/upload.ts +++ b/packages/eas-cli/src/metadata/upload.ts @@ -2,6 +2,7 @@ import fs from 'fs-extra'; import path from 'path'; import { MetadataEvent } from '../analytics/events'; +import Log from '../log'; import { AppleData } from './apple/data'; import { createAppleTasks } from './apple/tasks'; import { createAppleReader, validateConfig } from './config'; @@ -31,6 +32,9 @@ export async function uploadMetadataAsync(metadataCtx: MetadataContext): Promise throw new MetadataValidationError(`Store configuration errors found`, validationErrors); } + Log.addNewLineIfNone(); + Log.log('Uploading App Store configuration...'); + const errors: Error[] = []; const config = createAppleReader(fileData); const tasks = createAppleTasks(metadataCtx); diff --git a/packages/eas-cli/src/metadata/utils/__tests__/date.test.ts b/packages/eas-cli/src/metadata/utils/__tests__/date.test.ts index a08779429b..03e32a4ca6 100644 --- a/packages/eas-cli/src/metadata/utils/__tests__/date.test.ts +++ b/packages/eas-cli/src/metadata/utils/__tests__/date.test.ts @@ -4,7 +4,6 @@ describe(removeDatePrecision, () => { it('returns null for falsy values', () => { expect(removeDatePrecision(null)).toBeNull(); expect(removeDatePrecision(undefined)).toBeNull(); - expect(removeDatePrecision(false)).toBeNull(); }); it('returns null for invalid dates', () => { diff --git a/packages/eas-cli/src/metadata/utils/array.ts b/packages/eas-cli/src/metadata/utils/array.ts deleted file mode 100644 index ab00e45a3c..0000000000 --- a/packages/eas-cli/src/metadata/utils/array.ts +++ /dev/null @@ -1,4 +0,0 @@ -export function unique(items: T[]): T[] { - const set = new Set(items); - return [...set]; -} diff --git a/packages/eas-cli/src/metadata/utils/date.ts b/packages/eas-cli/src/metadata/utils/date.ts index 7ac64b9719..28f427518b 100644 --- a/packages/eas-cli/src/metadata/utils/date.ts +++ b/packages/eas-cli/src/metadata/utils/date.ts @@ -9,7 +9,7 @@ * "pointer": "/data/attributes/earliestReleaseDate" * } */ -export function removeDatePrecision(date: any): null | Date { +export function removeDatePrecision(date: null | undefined | string | number | Date): null | Date { if (date) { try { const result = new Date(date); diff --git a/packages/eas-cli/src/utils/expodash/__tests__/uniq-test.ts b/packages/eas-cli/src/utils/expodash/__tests__/uniq-test.ts new file mode 100644 index 0000000000..eb3e6de63f --- /dev/null +++ b/packages/eas-cli/src/utils/expodash/__tests__/uniq-test.ts @@ -0,0 +1,15 @@ +import uniq from '../uniq'; + +describe(uniq, () => { + it('returns unique numbers from a list', () => { + expect(uniq([1, 2, 2, 3, 4, 2, 3])).toEqual([1, 2, 3, 4]); + }); + + it('returns unique strings from a list', () => { + expect(uniq(['hi', 'hello', 'hello', 'ola'])).toEqual(['hi', 'hello', 'ola']); + }); + + it('returns unique mixed types from a list', () => { + expect(uniq([1, 2, 2, 'hi', 'hi', 'hello', 3])).toEqual([1, 2, 'hi', 'hello', 3]); + }); +}); diff --git a/packages/eas-cli/src/utils/expodash/uniq.ts b/packages/eas-cli/src/utils/expodash/uniq.ts new file mode 100644 index 0000000000..8f8b093cba --- /dev/null +++ b/packages/eas-cli/src/utils/expodash/uniq.ts @@ -0,0 +1,4 @@ +export default function uniq(items: T[]): T[] { + const set = new Set(items); + return [...set]; +}