diff --git a/.github/workflows/it-tests.yml b/.github/workflows/it-tests.yml index e2315d93a0..bdada59795 100644 --- a/.github/workflows/it-tests.yml +++ b/.github/workflows/it-tests.yml @@ -101,6 +101,7 @@ jobs: npx --yes verdaccio --config $GITHUB_WORKSPACE\\.verdaccio\\conf\\config-without-docker.yaml --listen http://127.0.0.1:4873 & npx --yes wait-on http://127.0.0.1:4873 -t 180000 fi + yarn verdaccio:login shell: bash - name: Test id: it-tests diff --git a/.verdaccio/conf/.npmrc b/.verdaccio/conf/.npmrc index d5231db0b7..0eb74370d4 100644 --- a/.verdaccio/conf/.npmrc +++ b/.verdaccio/conf/.npmrc @@ -1 +1 @@ -registry=http://127.0.0.1:4873 +registry=http://127.0.0.1:4873/ diff --git a/docs/cms-adapters/CMS_ADAPTERS.md b/docs/cms-adapters/CMS_ADAPTERS.md index 0ee4b92e25..b2b7acd3d6 100644 --- a/docs/cms-adapters/CMS_ADAPTERS.md +++ b/docs/cms-adapters/CMS_ADAPTERS.md @@ -163,8 +163,6 @@ __Note:__ The duplicate CSS Variable will be specified as warning and overridden ### Rules engine extractor -### Metadata - As for the other metadata retrieved, a bit of configuration is needed in order to extract metadata for facts and operators in rules engine scope. #### How to install @@ -225,3 +223,119 @@ Example: ... } ``` + +## How to check for breaking changes on metadata + +Version after version, it can be verified whether any breaking changes have been introduced, or if there is any metadata provided to document it. +To achieve this, simply configure the following builder in your `angular.json` file as shown below. + +```json5 +{ + // ..., + "projects": { + // ..., + "": { + // ..., + "architect": { + "check-config-migration-metadata": { + "builder": "@o3r/components:check-config-migration-metadata", + "options": { + "migrationDataPath": "./migration-scripts/MIGRATION-*.json", // Required + "granularity": "major", // Default value is minor + "allowBreakingChanges": true, // Default value is false + "packageManager": "npm", // If not provided, it will be determined based on the repository architecture + "metadataPath": "./component.config.metadata.json" // Default value + } + }, + "check-style-migration-metadata": { + "builder": "@o3r/styling:check-style-migration-metadata", + "options": { + "migrationDataPath": "./migration-scripts/MIGRATION-*.json" // Required + } + }, + "check-localization-migration-metadata": { + "builder": "@o3r/localization:check-localization-migration-metadata", + "options": { + "migrationDataPath": "./migration-scripts/MIGRATION-*.json" // Required + } + } + } + } + } +} +``` + +Example of migration file: +```json5 +{ + "$schema": "https://raw.githubusercontent.com/AmadeusITGroup/otter/main/packages/@o3r/extractors/schemas/migration.metadata.schema.json", + "version": "10.0.0", + "changes": [ + { // Move property to a new library and to a new config and rename property name + 'contentType': 'CONFIG', + 'before': { + 'libraryName': '@old/lib', + 'configName': 'OldConfig', + 'propertyName': 'oldName' + }, + 'after': { + 'libraryName': '@new/lib', + 'configName': 'NewConfig', + 'propertyName': 'newName' + } + }, + { // Rename configuration name for all properties + 'contentType': 'CONFIG', + 'before': { + 'libraryName': '@o3r/lib', + 'configName': 'OldConfig' + }, + 'after': { + 'libraryName': '@o3r/lib', + 'configName': 'NewConfig' + } + }, + { // Rename library name for all configurations + 'contentType': 'CONFIG', + 'before': { + 'libraryName': '@o3r/lib2' + }, + 'after': { + 'libraryName': '@o3r/lib3' + } + }, + { // Rename localization key + 'contentType': 'LOCALIZATION', + 'before': { + 'key': 'old-localization.key' + }, + 'after': { + 'key': 'new-localization.key' + } + }, + { // Rename CSS variable + 'contentType': 'STYLE', + 'before': { + 'name': 'old-css-var-name' + }, + 'after': { + 'name': 'new-css-var-name' + } + } + ] +} +``` + +These migrations files are also useful in the CMS to automate the migration of the database associated to the metadata. + +Make sure to expose them in the bundled application by adding them in the `files` field of your `package.json` and copy the files in the build process if needed + +```json5 +{ + "files": [ + "./migration-scripts/" + ] +} +``` + +Also make sure to place them in a folder name `migration-scripts` in your packaged app or to set the `migrationScriptFolder` in your `cms.json`. diff --git a/jest.config.it.js b/jest.config.it.js index 351473f020..84cb78effc 100644 --- a/jest.config.it.js +++ b/jest.config.it.js @@ -3,10 +3,11 @@ const getJestProjectConfig = require('./jest.config.ut').getJestProjectConfig; /** * @param rootDir {string} + * @param options.tsconfig {string} * @returns {import('ts-jest/dist/types').JestConfigWithTsJest} */ -module.exports.getJestConfig = (rootDir) => ({ - ...getJestProjectConfig(rootDir, false), +module.exports.getJestConfig = (rootDir, options) => ({ + ...getJestProjectConfig(rootDir, false, options), rootDir: '..', setupFilesAfterEnv: null, testPathIgnorePatterns: [ diff --git a/jest.config.ut.js b/jest.config.ut.js index 8e8e651813..8d41e24f9e 100644 --- a/jest.config.ut.js +++ b/jest.config.ut.js @@ -10,9 +10,10 @@ globalThis.ngJest = { * Jest configuration that can be set at project level * @param rootDir {string} * @param isAngularSetup {boolean} + * @param options.tsconfig {string} * @returns {import('ts-jest/dist/types').JestConfigWithTsJest} */ -module.exports.getJestProjectConfig = (rootDir, isAngularSetup) => { +module.exports.getJestProjectConfig = (rootDir, isAngularSetup, options) => { const relativePath = relative(rootDir, __dirname); const moduleNameMapper = Object.fromEntries( Object.entries(pathsToModuleNameMapper(compilerOptions.paths)) @@ -39,7 +40,7 @@ module.exports.getJestProjectConfig = (rootDir, isAngularSetup) => { '^.+\\.[mc]?tsx?$': [ 'ts-jest', { - tsconfig: '/tsconfig.spec.json', + tsconfig: options?.tsconfig ?? '/tsconfig.spec.json', stringifyContentPathRegex: '\\.html$' } ] @@ -56,7 +57,7 @@ module.exports.getJestProjectConfig = (rootDir, isAngularSetup) => { '^.+\\.tsx?$': [ 'jest-preset-angular', { - tsconfig: '/tsconfig.spec.json', + tsconfig: options?.tsconfig ?? '/tsconfig.spec.json', stringifyContentPathRegex: '\\.html$' } ] diff --git a/packages/@o3r/application/schemas/cms.schema.json b/packages/@o3r/application/schemas/cms.schema.json index a9ae757e92..4a6cd9fd3b 100644 --- a/packages/@o3r/application/schemas/cms.schema.json +++ b/packages/@o3r/application/schemas/cms.schema.json @@ -30,6 +30,10 @@ "functionalContentsFolder": { "type": "string", "description": "The relative path to the functional contents metadata folder to import" + }, + "migrationScriptFolder": { + "type": "string", + "description": "The relative path to the migration scripts" } } } diff --git a/packages/@o3r/application/schematics/ng-update/v10.0/update-cms-config.ts b/packages/@o3r/application/schematics/ng-update/v10.0/update-cms-config.ts index 351f9492cc..e6ccb22f77 100644 --- a/packages/@o3r/application/schematics/ng-update/v10.0/update-cms-config.ts +++ b/packages/@o3r/application/schematics/ng-update/v10.0/update-cms-config.ts @@ -22,7 +22,7 @@ export function updateCmsJsonFile(): Rule { }); Object.entries(filesToUpdate).forEach(([path, contentObj]) => { - contentObj.$schema = 'https://github.com/AmadeusITGroup/otter/blob/main/packages/@o3r/application/schemas/cms.json'; + contentObj.$schema = 'https://github.com/AmadeusITGroup/otter/blob/main/packages/@o3r/application/schemas/cms.schema.json'; tree.overwrite(path, JSON.stringify(contentObj, null, 2)); }); return tree; diff --git a/packages/@o3r/components/builders.json b/packages/@o3r/components/builders.json index 8459a059e5..10a8e41023 100644 --- a/packages/@o3r/components/builders.json +++ b/packages/@o3r/components/builders.json @@ -5,6 +5,11 @@ "implementation": "./builders/component-extractor/", "schema": "./builders/component-extractor/schema.json", "description": "Extract the component metadata (configuration and class) from an Otter project" + }, + "check-config-migration-metadata": { + "implementation": "./builders/metadata-check/", + "schema": "./builders/metadata-check/schema.json", + "description": "Check for component metadata breaking changes" } } } diff --git a/packages/@o3r/components/builders/metadata-check/helpers/config-metadata-comparison.helper.ts b/packages/@o3r/components/builders/metadata-check/helpers/config-metadata-comparison.helper.ts new file mode 100644 index 0000000000..1664f2ff3b --- /dev/null +++ b/packages/@o3r/components/builders/metadata-check/helpers/config-metadata-comparison.helper.ts @@ -0,0 +1,53 @@ +import type { ComponentConfigOutput } from '@o3r/components'; +import type { MetadataComparator } from '@o3r/extractors'; + +/** + * Interface describing a config migration element + */ +export interface MigrationConfigData { + /** Library name */ + libraryName: string; + /** + * Configuration name + */ + configName?: string; + /** + * Configuration property name + */ + propertyName?: string; +} + +/** + * Returns an array of config metadata from a metadata file. + * To be easily parseable, the properties will be split in separate items of the array. + * @param content Content of a migration metadata files + * @example Array conversion + * ```javascript + * [{ library: '@o3r/demo', properties: [{name : 'property1', type: 'string'}, {name : 'property2', type: 'number'}] }] + * ``` + * will become : + * ```javascript + * [{ library: '@o3r/demo', properties: [{name : 'property1', type: 'string'}] }, { library: '@o3r/demo', properties: [{name : 'property2', type: 'number'}] }] + * ``` + */ +const getConfigurationArray = (content: ComponentConfigOutput[]): ComponentConfigOutput[] => content.flatMap((config) => + config.properties.length > 1 + ? config.properties.map((prop) => ({...config, properties: [prop]})) + : [config] +); + +const getConfigurationPropertyName = (config: ComponentConfigOutput) => `${config.library}#${config.name}` + (config.properties.length ? ` ${config.properties[0].name}` : ''); + +const isMigrationConfigurationDataMatch = (config: ComponentConfigOutput, migrationData: MigrationConfigData) => + migrationData.libraryName === config.library + && (!migrationData.configName || migrationData.configName === config.name) + && (!migrationData.propertyName || config.properties[0]?.name === migrationData.propertyName); + +/** + * Comparator used to compare one version of config metadata with another + */ +export const configMetadataComparator: MetadataComparator = { + getArray: getConfigurationArray, + getIdentifier: getConfigurationPropertyName, + isMigrationDataMatch: isMigrationConfigurationDataMatch +}; diff --git a/packages/@o3r/components/builders/metadata-check/helpers/index.ts b/packages/@o3r/components/builders/metadata-check/helpers/index.ts new file mode 100644 index 0000000000..4d6911dfe8 --- /dev/null +++ b/packages/@o3r/components/builders/metadata-check/helpers/index.ts @@ -0,0 +1 @@ +export * from './config-metadata-comparison.helper'; diff --git a/packages/@o3r/components/builders/metadata-check/index.it.spec.ts b/packages/@o3r/components/builders/metadata-check/index.it.spec.ts new file mode 100644 index 0000000000..5128c07d27 --- /dev/null +++ b/packages/@o3r/components/builders/metadata-check/index.it.spec.ts @@ -0,0 +1,387 @@ +/** + * Test environment exported by O3rEnvironment, must be first line of the file + * @jest-environment @o3r/test-helpers/jest-environment + * @jest-environment-o3r-app-folder test-app-components-metadata-check + */ +const o3rEnvironment = globalThis.o3rEnvironment; + +import type { MigrationFile } from '@o3r/extractors'; +import { getPackageManager } from '@o3r/schematics'; +import { + getDefaultExecSyncOptions, + getLatestPackageVersion, + packageManagerAdd, + packageManagerExec, + packageManagerPublish, + packageManagerVersion +} from '@o3r/test-helpers'; +import { execFileSync } from 'node:child_process'; +import { promises, readFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { inc } from 'semver'; +import type { ComponentConfigOutput, ConfigProperty } from '@o3r/components'; +import type { MigrationConfigData } from './helpers/config-metadata-comparison.helper'; +import { getExternalDependenciesVersionRange } from '@o3r/schematics'; + +const baseVersion = '1.2.0'; +const version = '1.3.0'; +const migrationDataFileName = `MIGRATION-${version}.json`; +const metadataFileName = 'component.config.metadata.json'; + +const defaultMigrationData: MigrationFile = { + version, + changes: [ + { // Rename property name + 'contentType': 'CONFIG', + 'before': { + 'libraryName': '@o3r/lib1', + 'configName': 'MyConfig1', + 'propertyName': 'prop1' + }, + 'after': { + 'libraryName': '@o3r/lib1', + 'configName': 'MyConfig1', + 'propertyName': 'newProp1Name' + } + }, + { // Move property to a new config3 + 'contentType': 'CONFIG', + 'before': { + 'libraryName': '@o3r/lib2', + 'configName': 'MyConfig2', + 'propertyName': 'prop2' + }, + 'after': { + 'libraryName': '@o3r/lib2', + 'configName': 'NewConfig2', + 'propertyName': 'prop2' + } + }, + { // Move property to a new config and rename property name + 'contentType': 'CONFIG', + 'before': { + 'libraryName': '@o3r/lib3', + 'configName': 'MyConfig3', + 'propertyName': 'prop3' + }, + 'after': { + 'libraryName': '@o3r/lib3', + 'configName': 'NewConfig3', + 'propertyName': 'newProp3Name' + } + }, + { // Move property to a new library + 'contentType': 'CONFIG', + 'before': { + 'libraryName': '@o3r/lib4', + 'configName': 'MyConfig4', + 'propertyName': 'prop4' + }, + 'after': { + 'libraryName': '@new/lib4', + 'configName': 'MyConfig4', + 'propertyName': 'prop4' + } + }, + { // Move property to a new library and rename property name + 'contentType': 'CONFIG', + 'before': { + 'libraryName': '@o3r/lib5', + 'configName': 'MyConfig5', + 'propertyName': 'prop5' + }, + 'after': { + 'libraryName': '@new/lib5', + 'configName': 'MyConfig5', + 'propertyName': 'newProp5Name' + } + }, + { // Move property to a new library and to a new config and rename property name + 'contentType': 'CONFIG', + 'before': { + 'libraryName': '@o3r/lib6', + 'configName': 'MyConfig6', + 'propertyName': 'prop6' + }, + 'after': { + 'libraryName': '@new/lib6', + 'configName': 'NewConfig6', + 'propertyName': 'newProp6Name' + } + }, + { // Rename configuration name for all properties + 'contentType': 'CONFIG', + 'before': { + 'libraryName': '@o3r/lib7', + 'configName': 'MyConfig7' + }, + 'after': { + 'libraryName': '@o3r/lib7', + 'configName': 'NewConfig7' + } + }, + { // Rename library name for all configurations + 'contentType': 'CONFIG', + 'before': { + 'libraryName': '@o3r/lib8' + }, + 'after': { + 'libraryName': '@new/lib8' + } + }, + { // Move configuration to a new library + 'contentType': 'CONFIG', + 'before': { + 'libraryName': '@o3r/lib9', + 'configName': 'MyConfig9' + }, + 'after': { + 'libraryName': '@new/lib9', + 'configName': 'MyConfig9' + } + } + ] +}; + +const createProp = (name: string): ConfigProperty => ({ + name, + type: 'string', + description: '', + label: name +}); + +const createConfig = (library: string, name: string, propertiesName: string[]): ComponentConfigOutput => ({ + library, + name, + properties: propertiesName.map(createProp), + type: 'EXPOSED_COMPONENT', + path: '' +}); + +const previousConfigurationMetadata: ComponentConfigOutput[] = [ + createConfig('@o3r/lib0', 'MyConfig0', ['prop0']), + createConfig('@o3r/lib1', 'MyConfig1', ['prop1']), + createConfig('@o3r/lib2', 'MyConfig2', ['prop2']), + createConfig('@o3r/lib3', 'MyConfig3', ['prop3']), + createConfig('@o3r/lib4', 'MyConfig4', ['prop4']), + createConfig('@o3r/lib5', 'MyConfig5', ['prop5']), + createConfig('@o3r/lib6', 'MyConfig6', ['prop6']), + createConfig('@o3r/lib7', 'MyConfig7', ['prop7']), + createConfig('@o3r/lib8', 'MyConfig8', ['prop8']), + createConfig('@o3r/lib9', 'MyConfig9', ['prop9']) +]; + +const newConfigurationMetadata: ComponentConfigOutput[] = [ + previousConfigurationMetadata[0], + createConfig('@o3r/lib1', 'MyConfig1', ['newProp1Name']), + createConfig('@o3r/lib2', 'NewConfig2', ['prop2']), + createConfig('@o3r/lib3', 'NewConfig3', ['newProp3Name']), + createConfig('@new/lib4', 'MyConfig4', ['prop4']), + createConfig('@new/lib5', 'MyConfig5', ['newProp5Name']), + createConfig('@new/lib6', 'NewConfig6', ['newProp6Name']), + createConfig('@o3r/lib7', 'NewConfig7', ['prop7']), + createConfig('@new/lib8', 'MyConfig8', ['prop8']), + createConfig('@new/lib9', 'MyConfig9', ['prop9']) +]; + +function writeFileAsJSON(path: string, content: object) { + return promises.writeFile(path, JSON.stringify(content), { encoding: 'utf8' }); +} + +const initTest = async ( + allowBreakingChanges: boolean, + newMetadata: ComponentConfigOutput[], + migrationData: MigrationFile, + packageNameSuffix: string +) => { + const { workspacePath, appName, applicationPath, o3rVersion, isYarnTest } = o3rEnvironment.testEnvironment; + const execAppOptions = { ...getDefaultExecSyncOptions(), cwd: applicationPath }; + const execAppOptionsWorkspace = { ...getDefaultExecSyncOptions(), cwd: workspacePath }; + packageManagerAdd(`@o3r/components@${o3rVersion}`, execAppOptionsWorkspace); + packageManagerAdd(`@o3r/extractors@${o3rVersion}`, execAppOptionsWorkspace); + const versions = getExternalDependenciesVersionRange([ + 'semver', + ...(isYarnTest ? [ + '@yarnpkg/core', + '@yarnpkg/fslib', + '@yarnpkg/plugin-npm', + '@yarnpkg/plugin-pack', + '@yarnpkg/cli' + ] : []) + ], join(__dirname, '..', '..', 'package.json'), { + warn: jest.fn() + } as any); + Object.entries(versions).forEach(([pkgName, pkgVersion]) => packageManagerAdd(`${pkgName}@${pkgVersion}`, execAppOptionsWorkspace)); + const packageJsonPath = join(applicationPath, 'package.json'); + const angularJsonPath = join(workspacePath, 'angular.json'); + const metadataPath = join(applicationPath, metadataFileName); + const migrationDataPath = join(applicationPath, migrationDataFileName); + + // Add builder options + const angularJson = JSON.parse(readFileSync(angularJsonPath, { encoding: 'utf8' }).toString()); + const builderConfig = { + builder: '@o3r/components:check-config-migration-metadata', + options: { + allowBreakingChanges, + migrationDataPath: `**/MIGRATION-*.json` + } + }; + angularJson.projects[appName].architect['check-metadata'] = builderConfig; + await writeFileAsJSON(angularJsonPath, angularJson); + + // Add scope to project for registry management + let packageJson = JSON.parse(readFileSync(packageJsonPath, { encoding: 'utf8' }).toString()); + const packageName = `@o3r/${packageJson.name}-${packageNameSuffix}`; + packageJson = { + ...packageJson, + name: packageName, + private: false + }; + await writeFileAsJSON(packageJsonPath, packageJson); + + // Set old metadata and publish to registry + await writeFileAsJSON(metadataPath, previousConfigurationMetadata); + + let latestVersion; + try { + latestVersion = getLatestPackageVersion(packageName, execAppOptionsWorkspace); + } catch { + latestVersion = baseVersion; + } + + const bumpedVersion = inc(latestVersion, 'patch'); + + const args = getPackageManager() === 'yarn' ? [] : ['--no-git-tag-version', '-f']; + packageManagerVersion(bumpedVersion, args, execAppOptions); + + packageManagerPublish([], execAppOptions); + + // Override with new metadata for comparison + await writeFileAsJSON(metadataPath, newMetadata); + + // Add migration data file + await writeFileAsJSON(migrationDataPath, migrationData); +}; + +describe('check metadata migration', () => { + beforeEach(async () => { + const { applicationPath } = o3rEnvironment.testEnvironment; + const execAppOptions = { ...getDefaultExecSyncOptions(), cwd: applicationPath, shell: true }; + await promises.copyFile( + join(__dirname, '..', '..', '..', '..', '..', '.verdaccio', 'conf', '.npmrc'), + join(applicationPath, '.npmrc') + ); + execFileSync('npx', [ + '--yes', + 'npm-cli-login', + '-u', + 'verdaccio', + '-p', + 'verdaccio', + '-e', + 'test@test.com', + '-r', + 'http://127.0.0.1:4873' + ], execAppOptions); + }); + + test('should not throw', async () => { + await initTest( + true, + newConfigurationMetadata, + defaultMigrationData, + 'allow-breaking-changes' + ); + const { workspacePath, appName } = o3rEnvironment.testEnvironment; + const execAppOptionsWorkspace = { ...getDefaultExecSyncOptions(), cwd: workspacePath }; + + expect(() => packageManagerExec({ script: 'ng', args: ['run', `${appName}:check-metadata`] }, execAppOptionsWorkspace)).not.toThrow(); + }); + + test('should throw because no migration data', async () => { + await initTest( + true, + newConfigurationMetadata, + { + ...defaultMigrationData, + changes: [] + }, + 'no-migration-data' + ); + const { workspacePath, appName } = o3rEnvironment.testEnvironment; + const execAppOptionsWorkspace = { ...getDefaultExecSyncOptions(), cwd: workspacePath }; + + try { + packageManagerExec({ script: 'ng', args: ['run', `${appName}:check-metadata`] }, execAppOptionsWorkspace); + throw new Error('should have thrown before'); + } catch (e: any) { + expect(e.message).not.toBe('should have thrown before'); + previousConfigurationMetadata.slice(1).forEach(({ library, name, properties }) => { + const id = `${library}#${name} ${properties[0].name}`; + expect(e.message).toContain(`Property ${id} has been modified but is not documented in the migration document`); + expect(e.message).not.toContain(`Property ${id} has been modified but the new property is not present in the new metadata`); + expect(e.message).not.toContain(`Property ${id} is not present in the new metadata and breaking changes are not allowed`); + }); + } + }); + + test('should throw because migration data invalid', async () => { + await initTest( + true, + [newConfigurationMetadata[0]], + { + ...defaultMigrationData, + changes: defaultMigrationData.changes.map((change) => ({ + ...change, + after: { + ...change.after, + libraryName: '@invalid/lib' + } + })) + }, + 'invalid-data' + ); + const { workspacePath, appName } = o3rEnvironment.testEnvironment; + const execAppOptionsWorkspace = { ...getDefaultExecSyncOptions(), cwd: workspacePath }; + + try { + packageManagerExec({ script: 'ng', args: ['run', `${appName}:check-metadata`] }, execAppOptionsWorkspace); + throw new Error('should have thrown before'); + } catch (e: any) { + expect(e.message).not.toBe('should have thrown before'); + previousConfigurationMetadata.slice(1).forEach(({ library, name, properties }) => { + const id = `${library}#${name} ${properties[0].name}`; + expect(e.message).not.toContain(`Property ${id} has been modified but is not documented in the migration document`); + expect(e.message).toContain(`Property ${id} has been modified but the new property is not present in the new metadata`); + expect(e.message).not.toContain(`Property ${id} is not present in the new metadata and breaking changes are not allowed`); + }); + } + }); + + test('should throw because breaking changes are not allowed', async () => { + await initTest( + false, + newConfigurationMetadata, + { + ...defaultMigrationData, + changes: [] + }, + 'breaking-changes' + ); + const { workspacePath, appName } = o3rEnvironment.testEnvironment; + const execAppOptionsWorkspace = { ...getDefaultExecSyncOptions(), cwd: workspacePath }; + + try { + packageManagerExec({ script: 'ng', args: ['run', `${appName}:check-metadata`] }, execAppOptionsWorkspace); + throw new Error('should have thrown before'); + } catch (e: any) { + expect(e.message).not.toBe('should have thrown before'); + previousConfigurationMetadata.slice(1).forEach(({ library, name, properties }) => { + const id = `${library}#${name} ${properties[0].name}`; + expect(e.message).not.toContain(`Property ${id} has been modified but is not documented in the migration document`); + expect(e.message).not.toContain(`Property ${id} has been modified but the new property is not present in the new metadata`); + expect(e.message).toContain(`Property ${id} is not present in the new metadata and breaking changes are not allowed`); + }); + } + }); +}); diff --git a/packages/@o3r/components/builders/metadata-check/index.ts b/packages/@o3r/components/builders/metadata-check/index.ts new file mode 100644 index 0000000000..8d75e27b9d --- /dev/null +++ b/packages/@o3r/components/builders/metadata-check/index.ts @@ -0,0 +1,8 @@ +import { type BuilderOutput, createBuilder } from '@angular-devkit/architect'; +import { checkMetadataBuilder, createBuilderWithMetricsIfInstalled } from '@o3r/extractors'; +import { configMetadataComparator } from './helpers/config-metadata-comparison.helper'; +import type { ConfigMigrationMetadataCheckBuilderSchema } from './schema'; + +export default createBuilder(createBuilderWithMetricsIfInstalled((options, context): Promise => { + return checkMetadataBuilder(options, context, configMetadataComparator); +})); diff --git a/packages/@o3r/components/builders/metadata-check/schema.json b/packages/@o3r/components/builders/metadata-check/schema.json new file mode 100644 index 0000000000..4a55765ac9 --- /dev/null +++ b/packages/@o3r/components/builders/metadata-check/schema.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "type": "object", + "$id": "ConfigMigrationMetadataCheckBuilderSchema", + "title": "Check config migration metadata builder", + "description": "Check config migration metadata builder", + "properties": { + "migrationDataPath": { + "type": ["string", "array"], + "items": { + "type": "string" + }, + "description": "Glob of the migration files to use." + }, + "granularity": { + "type": "string", + "description": "Granularity of the migration check.", + "default": "minor", + "enum": [ + "major", + "minor" + ] + }, + "allowBreakingChanges": { + "type": "boolean", + "description": "Are breaking changes allowed.", + "default": false + }, + "packageManager": { + "type": "string", + "description": "Override of the package manager, otherwise it will be computed from the project setup." + }, + "metadataPath": { + "type": "string", + "description": "Path of the config metadata file.", + "default": "./component.config.metadata.json" + } + }, + "additionalProperties": false, + "required": ["migrationDataPath"] +} diff --git a/packages/@o3r/components/builders/metadata-check/schema.ts b/packages/@o3r/components/builders/metadata-check/schema.ts new file mode 100644 index 0000000000..77eebbea72 --- /dev/null +++ b/packages/@o3r/components/builders/metadata-check/schema.ts @@ -0,0 +1,5 @@ +import type { MigrationMetadataCheckBuilderOptions } from '@o3r/extractors'; + +/** Migration metadata check builder schema */ +export interface ConfigMigrationMetadataCheckBuilderSchema extends MigrationMetadataCheckBuilderOptions { +} diff --git a/packages/@o3r/components/package.json b/packages/@o3r/components/package.json index 3d6b5a6ecb..15ce6d5bae 100644 --- a/packages/@o3r/components/package.json +++ b/packages/@o3r/components/package.json @@ -53,10 +53,16 @@ "@o3r/schematics": "workspace:^", "@o3r/testing": "workspace:^", "@schematics/angular": "~18.0.0", + "@yarnpkg/cli": "^4.0.0", + "@yarnpkg/core": "^4.1.1", + "@yarnpkg/fslib": "^3.1.0", + "@yarnpkg/plugin-npm": "^3.0.1", + "@yarnpkg/plugin-pack": "^4.0.0", "chokidar": "^3.5.2", "globby": "^11.1.0", "jsonpath-plus": "^9.0.0", "rxjs": "^7.8.1", + "semver": "^7.5.2", "typescript": "~5.4.2" }, "peerDependenciesMeta": { @@ -90,6 +96,21 @@ "@schematics/angular": { "optional": true }, + "@yarnpkg/cli": { + "optional": true + }, + "@yarnpkg/core": { + "optional": true + }, + "@yarnpkg/fslib": { + "optional": true + }, + "@yarnpkg/plugin-npm": { + "optional": true + }, + "@yarnpkg/plugin-pack": { + "optional": true + }, "chokidar": { "optional": true }, @@ -99,6 +120,9 @@ "jsonpath-plus": { "optional": true }, + "semver": { + "optional": true + }, "typescript": { "optional": true } @@ -145,9 +169,15 @@ "@stylistic/eslint-plugin-ts": "^2.0.0", "@types/jest": "~29.5.2", "@types/node": "^20.0.0", + "@types/semver": "^7.3.13", "@typescript-eslint/eslint-plugin": "^7.14.1", "@typescript-eslint/parser": "^7.14.1", "@typescript-eslint/utils": "^7.14.1", + "@yarnpkg/cli": "^4.3.1", + "@yarnpkg/core": "^4.1.1", + "@yarnpkg/fslib": "^3.1.0", + "@yarnpkg/plugin-npm": "^3.0.1", + "@yarnpkg/plugin-pack": "^4.0.0", "chokidar": "^3.5.2", "cpy-cli": "^5.0.0", "eslint": "^8.57.0", @@ -170,6 +200,7 @@ "semver": "^7.5.2", "ts-jest": "~29.2.0", "ts-node": "~10.9.2", + "type-fest": "^4.10.2", "typescript": "~5.4.2", "unionfs": "~4.5.1", "zone.js": "~0.14.2" diff --git a/packages/@o3r/components/schematics/cms-adapter/index.ts b/packages/@o3r/components/schematics/cms-adapter/index.ts index d133264365..42814f1132 100644 --- a/packages/@o3r/components/schematics/cms-adapter/index.ts +++ b/packages/@o3r/components/schematics/cms-adapter/index.ts @@ -39,6 +39,13 @@ function updateCmsAdapterFn(options: { projectName?: string | undefined }): Rule } }; + workspaceProject.architect['check-config-migration-metadata'] ||= { + builder: '@o3r/components:check-config-migration-metadata', + options: { + migrationDataPath: 'MIGRATION-*.json' + } + }; + workspace.projects[options.projectName!] = workspaceProject; tree.overwrite('/angular.json', JSON.stringify(workspace, null, 2)); return tree; diff --git a/packages/@o3r/extractors/package.json b/packages/@o3r/extractors/package.json index dfeabe01d0..d9818ce46a 100644 --- a/packages/@o3r/extractors/package.json +++ b/packages/@o3r/extractors/package.json @@ -16,16 +16,25 @@ "ng": "yarn nx", "build": "yarn nx build extractors", "postbuild": "patch-package-json-main", - "prepare:build:builders": "yarn cpy 'schematics/**/*.json' 'schematics/**/templates/**' dist/schematics && yarn cpy '{collection,migration}.json' dist", + "prepare:build:builders": "yarn cpy 'schematics/**/*.json' 'schematics/**/templates/**' dist/schematics && yarn cpy '{collection,migration}.json' dist && yarn cpy 'schemas/*.json' 'dist/schemas'", "build:builders": "tsc -b tsconfig.builders.json --pretty && yarn generate-cjs-manifest", "prepare:publish": "prepare-publish ./dist" }, "peerDependencies": { + "@angular-devkit/architect": "~0.1800.0", "@angular-devkit/core": "~18.0.0", "@o3r/core": "workspace:^", "@o3r/schematics": "workspace:^", "@o3r/telemetry": "workspace:^", "@schematics/angular": "~18.0.0", + "@yarnpkg/cli": "^4.0.0", + "@yarnpkg/core": "^4.1.1", + "@yarnpkg/fslib": "^3.1.0", + "@yarnpkg/plugin-npm": "^3.0.1", + "@yarnpkg/plugin-pack": "^4.0.0", + "globby": "^11.1.0", + "semver": "^7.5.2", + "type-fest": "^4.10.2", "typescript": "~5.4.2" }, "peerDependenciesMeta": { @@ -34,6 +43,30 @@ }, "@schematics/angular": { "optional": true + }, + "@yarnpkg/cli": { + "optional": true + }, + "@yarnpkg/core": { + "optional": true + }, + "@yarnpkg/fslib": { + "optional": true + }, + "@yarnpkg/plugin-npm": { + "optional": true + }, + "@yarnpkg/plugin-pack": { + "optional": true + }, + "globby": { + "optional": true + }, + "semver": { + "optional": true + }, + "type-fest": { + "optional": true } }, "dependencies": { @@ -75,9 +108,15 @@ "@types/inquirer": "~8.2.10", "@types/jest": "~29.5.2", "@types/node": "^20.0.0", + "@types/semver": "^7.3.13", "@typescript-eslint/eslint-plugin": "^7.14.1", "@typescript-eslint/parser": "^7.14.1", "@typescript-eslint/utils": "^7.14.1", + "@yarnpkg/cli": "^4.3.1", + "@yarnpkg/core": "^4.1.1", + "@yarnpkg/fslib": "^3.1.0", + "@yarnpkg/plugin-npm": "^3.0.1", + "@yarnpkg/plugin-pack": "^4.0.0", "cpy-cli": "^5.0.0", "eslint": "^8.57.0", "eslint-import-resolver-node": "^0.3.9", @@ -85,6 +124,7 @@ "eslint-plugin-jsdoc": "~48.7.0", "eslint-plugin-prefer-arrow": "~1.2.3", "eslint-plugin-unicorn": "^54.0.0", + "globby": "^11.1.0", "intl-messageformat": "~10.5.1", "jest": "~29.7.0", "jest-environment-jsdom": "~29.7.0", diff --git a/packages/@o3r/extractors/schemas/migration.metadata.schema.json b/packages/@o3r/extractors/schemas/migration.metadata.schema.json new file mode 100644 index 0000000000..9b702cf51e --- /dev/null +++ b/packages/@o3r/extractors/schemas/migration.metadata.schema.json @@ -0,0 +1,133 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "MigrationMetadataSchema", + "description": "Schema of migration metadata", + "type": "object", + "required": [ + "version", + "changes" + ], + "properties": { + "version": { + "type": "string", + "description": "Version of the documented migration" + }, + "changes": { + "type": "array", + "description": "List of all the changes contained in this version", + "items": { + "oneOf": [ + { "$ref": "#/definitions/ConfigChange" }, + { "$ref": "#/definitions/LocalizationChange" }, + { "$ref": "#/definitions/StyleChange" } + ] + } + } + }, + "definitions": { + "ConfigMigrationItem": { + "type": "object", + "required": ["libraryName"], + "properties": { + "libraryName": { + "type": "string", + "description": "Library name" + }, + "configName": { + "type": "string", + "description": "Configuration name" + }, + "propertyName": { + "type": "string", + "description": "Configuration property name" + } + } + }, + "LocalizationMigrationItem": { + "type": "object", + "required": ["key"], + "properties": { + "key": { + "type": "string", + "description": "Localization key" + } + } + }, + "StyleMigrationItem": { + "type": "object", + "required": ["name"], + "properties": { + "name": { + "type": "string", + "description": "CSS variable name" + } + } + }, + "ConfigChange": { + "type": "object", + "required": [ + "contentType", + "before" + ], + "properties": { + "contentType": { + "type": "string", + "description": "Metadata type", + "const": "CONFIG" + }, + "before": { + "description": "Previous metadata value", + "$ref": "#/definitions/ConfigMigrationItem" + }, + "after": { + "description": "New metadata value", + "$ref": "#/definitions/ConfigMigrationItem" + } + } + }, + "LocalizationChange": { + "type": "object", + "required": [ + "contentType", + "before" + ], + "properties": { + "contentType": { + "type": "string", + "description": "Metadata type", + "const": "LOCALIZATION" + }, + "before": { + "description": "Previous metadata value", + "$ref": "#/definitions/LocalizationMigrationItem" + }, + "after": { + "description": "New metadata value", + "$ref": "#/definitions/LocalizationMigrationItem" + } + } + }, + "StyleChange": { + "type": "object", + "required": [ + "contentType", + "before" + ], + "properties": { + "contentType": { + "type": "string", + "description": "Metadata type", + "const": "STYLE" + }, + "before": { + "description": "Previous metadata value", + "$ref": "#/definitions/StyleMigrationItem" + }, + "after": { + "description": "New metadata value", + "$ref": "#/definitions/StyleMigrationItem" + } + } + } + } +} diff --git a/packages/@o3r/extractors/schematics/cms-adapter/templates/cms.json.template b/packages/@o3r/extractors/schematics/cms-adapter/templates/cms.json.template index 072cb0619e..cadad26cab 100644 --- a/packages/@o3r/extractors/schematics/cms-adapter/templates/cms.json.template +++ b/packages/@o3r/extractors/schematics/cms-adapter/templates/cms.json.template @@ -1,3 +1,3 @@ { - "$schema": "https://github.com/AmadeusITGroup/otter/blob/main/packages/@o3r/application/schemas/cms.json" + "$schema": "https://github.com/AmadeusITGroup/otter/blob/main/packages/@o3r/application/schemas/cms.schema.json" } diff --git a/packages/@o3r/extractors/src/core/comparator/index.ts b/packages/@o3r/extractors/src/core/comparator/index.ts new file mode 100644 index 0000000000..5ea82c2ede --- /dev/null +++ b/packages/@o3r/extractors/src/core/comparator/index.ts @@ -0,0 +1,3 @@ +export * from './metadata-comparator.interface'; +export * from './metadata-comparison.helper'; +export * from './metadata-files.helper'; diff --git a/packages/@o3r/extractors/src/core/comparator/metadata-comparator.interface.ts b/packages/@o3r/extractors/src/core/comparator/metadata-comparator.interface.ts new file mode 100644 index 0000000000..3e232b5885 --- /dev/null +++ b/packages/@o3r/extractors/src/core/comparator/metadata-comparator.interface.ts @@ -0,0 +1,80 @@ +import type { JsonObject } from '@angular-devkit/core'; +import type { SupportedPackageManagers } from '@o3r/schematics'; + +/** + * Interface of the comparator used to compare 2 different versions of the same metadata file. + */ +export interface MetadataComparator { + /** + * Get an array of metadata items to parse a metadata file content. + * @param content Content of a metadata file + */ + getArray: (content: MetadataFile) => MetadataItem[]; + + /** + * Get the identifier of a metadata item. + * @param item Metadata item + */ + getIdentifier: (item: MetadataItem) => string; + + /** + * Compares 2 metadata items. + * @param item1 Metadata item + * @param item2 Metadata item to compare with + */ + isSame?: (item1: MetadataItem, item2: MetadataItem) => boolean; + + /** + * Returns true if a migration item matches a metadata item. + * @param metadataItem Metadata item + * @param migrationItem Migration item + */ + isMigrationDataMatch: (metadataItem: MetadataItem, migrationItem: MigrationMetadataItem) => boolean; +} + +/** + * Migration item used to document a migration + */ +export interface MigrationData { + /** Metadata type */ + contentType: string; + + /** Previous metadata value */ + before: MigrationMetadataItem; + + /** New metadata value */ + after?: MigrationMetadataItem; +} + +/** + * Migration file data structure. + */ +export interface MigrationFile { + /** Version of the documented migration */ + version: string; + + /** List of all the changes contained in this version */ + changes: MigrationData[]; +} + +export type MigrationCheckGranularity = 'major' | 'minor'; + +/** + * Generic metadata builder options + */ +export interface MigrationMetadataCheckBuilderOptions extends JsonObject { + /** Path to the folder containing the migration metadata. */ + migrationDataPath: string | string[]; + + /** Granularity of the migration check. */ + granularity: MigrationCheckGranularity; + + /** Whether breaking changes are allowed.*/ + allowBreakingChanges: boolean; + + /** Override of the package manager, otherwise it will be determined from the project. */ + packageManager: SupportedPackageManagers; + + /** Path of the metadata file to check */ + metadataPath: string; +} diff --git a/packages/@o3r/extractors/src/core/comparator/metadata-comparison.helper.ts b/packages/@o3r/extractors/src/core/comparator/metadata-comparison.helper.ts new file mode 100644 index 0000000000..4c8d89364f --- /dev/null +++ b/packages/@o3r/extractors/src/core/comparator/metadata-comparison.helper.ts @@ -0,0 +1,142 @@ +import type { BuilderContext, BuilderOutput } from '@angular-devkit/architect'; +import type { JsonObject } from '@angular-devkit/core'; +import { getPackageManagerInfo, O3rCliError, type PackageManagerOptions, type SupportedPackageManagers, type WorkspaceSchema } from '@o3r/schematics'; +import { sync as globbySync } from 'globby'; +import { existsSync, readFileSync } from 'node:fs'; +import { EOL } from 'node:os'; +import { join, posix } from 'node:path'; +import type { MetadataComparator, MigrationData, MigrationFile, MigrationMetadataCheckBuilderOptions } from './metadata-comparator.interface'; +import { getFilesFromRegistry, getLatestMigrationMetadataFile, getLocalMetadataFile, getVersionRangeFromLatestVersion } from './metadata-files.helper'; + +function checkMetadataFile( + lastMetadataFile: MetadataFile, + newMetadataFile: MetadataFile, + migrationData: MigrationData[], + isBreakingChangeAllowed: boolean, + comparator: MetadataComparator +): Error[] { + const errors: Error[] = []; + const newMetadataArray = comparator.getArray(newMetadataFile); + const lastMetadataArray = comparator.getArray(lastMetadataFile); + for (const lastValue of lastMetadataArray) { + const isInNewMetadata = newMetadataArray.some((newValue) => + comparator.isSame + ? comparator.isSame(newValue, lastValue) + : comparator.getIdentifier(newValue) === comparator.getIdentifier(lastValue) + ); + if (!isInNewMetadata) { + if (!isBreakingChangeAllowed) { + errors.push(new Error(`Property ${comparator.getIdentifier(lastValue)} is not present in the new metadata and breaking changes are not allowed`)); + continue; + } + + const migrationMetadataValue = migrationData.find((metadata) => comparator.isMigrationDataMatch(lastValue, metadata.before)); + + if (!migrationMetadataValue) { + errors.push(new Error(`Property ${comparator.getIdentifier(lastValue)} has been modified but is not documented in the migration document`)); + continue; + } + + if (migrationMetadataValue.after) { + const isNewValueInNewMetadata = newMetadataArray.some((newValue) => comparator.isMigrationDataMatch(newValue, migrationMetadataValue.after!)); + if (!isNewValueInNewMetadata) { + errors.push(new Error(`Property ${comparator.getIdentifier(lastValue)} has been modified but the new property is not present in the new metadata`)); + continue; + } + } + } + } + return errors; +} + +/** + * Gets the package manager to use to retrieve the previous package from npm. + * If the project uses npm or yarn 1 it will be npm. + * If the project uses yarn 2+ it will be yarn. + * This is especially important because npm and yarn 1 use the authentication from the .npmrc while yarn 2+ uses the .yarnrc. + * @param options Option to determine the final package manager + */ +function getPackageManagerForRegistry(options?: PackageManagerOptions): SupportedPackageManagers | undefined { + const packageManagerInfo = getPackageManagerInfo(options); + if (!packageManagerInfo.version) { + return undefined; + } + return packageManagerInfo.name === 'yarn' && !packageManagerInfo.version.match(/^1\./) ? 'yarn' : 'npm'; +} + +/** + * Checks a type of metadata against a previous version of these metadata extracted from a npm package. + * Will return errors if some changes are breaking and not allowed, or if the changes are not documented in the file + * provided in options. + * @param options Options for the builder + * @param context Builder context (from another builder) + * @param comparator Comparator implementation, depends on the type of metadata to check + */ +export async function checkMetadataBuilder( + options: MigrationMetadataCheckBuilderOptions, + context: BuilderContext, + comparator: MetadataComparator +): Promise { + context.reportRunning(); + const angularJsonPath = join(context.workspaceRoot, 'angular.json'); + const builderName = context.builder.name as string; + const angularJson = existsSync(angularJsonPath) ? JSON.parse(readFileSync(angularJsonPath, { encoding: 'utf8' }).toString()) as WorkspaceSchema : undefined; + if (!angularJson) { + context.logger.warn(`angular.json file cannot be found by ${builderName} builder. +Detection of package manager runner will fallback on the one used to execute the actual command.`); + } + + const packageManager = getPackageManagerForRegistry({ + workspaceConfig: angularJson, + enforcedNpmManager: options.packageManager + }); + + if (!packageManager) { + throw new O3rCliError(`The package manager to use could not be determined by ${builderName}. Try to override it using the packageManager option.`); + } + + const projectRoot = context.target && angularJson + ? join(context.workspaceRoot, angularJson.projects[context.target.project].root) + : context.currentDirectory; + const packageJsonPath = join(projectRoot, 'package.json'); + const packageJson = existsSync(packageJsonPath) ? JSON.parse(readFileSync(packageJsonPath, { encoding: 'utf8' }).toString()) as JsonObject : undefined; + if (!packageJson) { + throw new O3rCliError(`package.json file cannot be found by ${builderName} builder.`); + } + + const migrationDataFiles = globbySync(options.migrationDataPath, { cwd: context.currentDirectory }); + + const {path: migrationFilePath, version: latestMigrationVersion} = await getLatestMigrationMetadataFile(migrationDataFiles) || {}; + + if (!migrationFilePath || !latestMigrationVersion) { + throw new O3rCliError(`No migration data could be found matching ${typeof options.migrationDataPath === 'string' ? options.migrationDataPath : options.migrationDataPath.join(',')}`); + } + + context.logger.info(`Latest version present in migration data folder: ${latestMigrationVersion}`); + const previousVersion = await getVersionRangeFromLatestVersion(latestMigrationVersion, options.granularity); + + const migrationData = getLocalMetadataFile>(migrationFilePath); + + const packageLocator = `${packageJson.name as string}@${previousVersion}`; + context.logger.info(`Fetching ${packageLocator} from the registry.`); + const previousFile = await getFilesFromRegistry(packageLocator, [options.metadataPath], packageManager, context.workspaceRoot); + + const metadataPathInWorkspace = posix.join(projectRoot, options.metadataPath); + const newFile = getLocalMetadataFile(metadataPathInWorkspace); + + const metadata = JSON.parse(previousFile[options.metadataPath]) as MetadataFile; + + const errors = checkMetadataFile(metadata, newFile, migrationData.changes, options.allowBreakingChanges, comparator); + + if (errors.length) { + return { + success: false, + error: errors.map(({ message }) => message).join(EOL) + }; + } else { + context.logger.info('Migration data has been checked without errors.'); + return { + success: true + }; + } +} diff --git a/packages/@o3r/extractors/src/core/comparator/metadata-files.helper.spec.ts b/packages/@o3r/extractors/src/core/comparator/metadata-files.helper.spec.ts new file mode 100644 index 0000000000..88d86dfa33 --- /dev/null +++ b/packages/@o3r/extractors/src/core/comparator/metadata-files.helper.spec.ts @@ -0,0 +1,106 @@ +const mockBaseName = jest.fn(); +jest.mock('node:path', () => { + const original = jest.requireActual('node:path'); + return { + ...original, + basename: mockBaseName + }; +}); +const mockGt = jest.fn(); +const mockCoerce = jest.fn(); +jest.mock('semver', () => { + const original = jest.requireActual('semver'); + return { + ...original, + coerce: mockCoerce, + gt: mockGt + }; +}); +const mockNpmGetFilesFromRegistry = jest.fn(); +jest.mock('./package-managers-extractors/npm-file-extractor.helper', () => ({ + getFilesFromRegistry: mockNpmGetFilesFromRegistry +})); +const mockYarnGetFilesFromRegistry = jest.fn(); +jest.mock('./package-managers-extractors/yarn2-file-extractor.helper', () => ({ + getFilesFromRegistry: mockYarnGetFilesFromRegistry +})); + +import { + getFilesFromRegistry, + getLatestMigrationMetadataFile, + getVersionRangeFromLatestVersion +} from './metadata-files.helper'; + +const getFakePath = (fileName: string) => `path/${fileName}`; + +describe('metadata files helpers', () => { + beforeEach(() => { + mockBaseName.mockReset(); + mockGt.mockReset(); + mockCoerce.mockReset(); + mockNpmGetFilesFromRegistry.mockReset(); + mockYarnGetFilesFromRegistry.mockReset(); + }); + + describe('getLatestMigrationMetadataFile', () => { + it('should return undefined', async () => { + const fileName = 'migration.json'; + mockBaseName.mockReturnValueOnce(fileName); + await expect(getLatestMigrationMetadataFile([getFakePath(fileName)])).resolves.toBeUndefined(); + }); + + it('should return 1.23', async () => { + const firstFileName = 'migration-1.1.json'; + const secondFileName = 'migration-1.23.json'; + const thirdFileName = 'migration-1.0.json'; + + mockBaseName + .mockReturnValueOnce(firstFileName) + .mockReturnValueOnce(secondFileName) + .mockReturnValueOnce(thirdFileName); + mockGt + .mockReturnValueOnce(true) + .mockReturnValueOnce(false); + + await expect(getLatestMigrationMetadataFile([ + getFakePath(firstFileName), + getFakePath(secondFileName), + getFakePath(thirdFileName) + ])).resolves.toEqual({ + version: '1.23', + path: getFakePath(secondFileName) + }); + }); + }); + + describe('getVersionRangeFromLatestVersion', () => { + it('should throw an error', async () => { + const invalidVersion = 'invalid-version'; + const expectedErrorMessage = new RegExp(`${invalidVersion} is not a valid version.`); + await expect(getVersionRangeFromLatestVersion(invalidVersion, 'major')).rejects.toThrow(expectedErrorMessage); + await expect(getVersionRangeFromLatestVersion(invalidVersion, 'minor')).rejects.toThrow(expectedErrorMessage); + }); + + it('should return the good granularity version', async () => { + const major = 1; + const minor = 3; + mockCoerce.mockReturnValue({ major, minor}); + await expect(getVersionRangeFromLatestVersion(`${major}.${minor}.14`, 'major')).resolves.toBe(`<${major}.0.0`); + await expect(getVersionRangeFromLatestVersion(`${major}.${minor}.14`, 'minor')).resolves.toBe(`<${major}.${minor}.0`); + }); + }); + + describe('getFilesFromRegistry', () => { + it('should call getFilesFromRegistry from npm helpers', async () => { + await getFilesFromRegistry('', [], 'npm'); + expect(mockNpmGetFilesFromRegistry).toHaveBeenCalled(); + expect(mockYarnGetFilesFromRegistry).not.toHaveBeenCalled(); + }); + + it('should call getFilesFromRegistry from yarn helpers', async () => { + await getFilesFromRegistry('', [], 'yarn'); + expect(mockNpmGetFilesFromRegistry).not.toHaveBeenCalled(); + expect(mockYarnGetFilesFromRegistry).toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/@o3r/extractors/src/core/comparator/metadata-files.helper.ts b/packages/@o3r/extractors/src/core/comparator/metadata-files.helper.ts new file mode 100644 index 0000000000..d763f490bd --- /dev/null +++ b/packages/@o3r/extractors/src/core/comparator/metadata-files.helper.ts @@ -0,0 +1,58 @@ +import { O3rCliError, type SupportedPackageManagers } from '@o3r/schematics'; +import { readFileSync } from 'node:fs'; +import { basename } from 'node:path'; +import type { MigrationCheckGranularity } from './metadata-comparator.interface'; + +/** + * Returns a file from an npm package. + * @param packageRef Npm compatible package descriptor (version or range) + * @param filePaths List of files paths to extract + * @param packageManager Name of the package manager to use + * @param cwd working directory + */ +export async function getFilesFromRegistry(packageRef: string, filePaths: string[], packageManager: SupportedPackageManagers, cwd = process.cwd()): Promise<{[key: string]: string}> { + const npmFileExtractor = await import(packageManager === 'npm' + ? './package-managers-extractors/npm-file-extractor.helper' + : './package-managers-extractors/yarn2-file-extractor.helper' + ); + return npmFileExtractor.getFilesFromRegistry(packageRef, filePaths, cwd); +} + +/** + * Read and parses a JSON file + * @param metadataPath Path of the file + */ +export function getLocalMetadataFile(metadataPath: string): T { + const migrationData = readFileSync(metadataPath).toString(); + return JSON.parse(migrationData) as T; +} + +/** + * Given a path to a folder and a name pattern, returns the content of the file with the latest version in its name. + * @param migrationDataFiles Migration data files paths + */ +export async function getLatestMigrationMetadataFile(migrationDataFiles: string[]): Promise<{version: string; path: string} | undefined> { + let latestVersionFile: {version: string; path: string} | undefined; + for (const filePath of migrationDataFiles) { + const version = /\d+(?:\.\d+)*/.exec(basename(filePath))?.[0]; + if (version) { + const { gt } = await import('semver'); + latestVersionFile = !latestVersionFile || gt(version, latestVersionFile.version) ? {version, path: filePath} : latestVersionFile; + } + } + return latestVersionFile; +} + +/** + * Returns the range of package versions from which we will get the previous version according to granularity. + * @param latestMigrationVersion Latest version in the migration files + * @param granularity 'major' or 'minor' + */ +export async function getVersionRangeFromLatestVersion(latestMigrationVersion: string, granularity: MigrationCheckGranularity): Promise { + const { coerce } = await import('semver'); + const semver = coerce(latestMigrationVersion); + if (!semver) { + throw new O3rCliError(`${latestMigrationVersion} is not a valid version.`); + } + return `<${semver.major}.${granularity === 'minor' ? semver.minor : 0}.0`; +} diff --git a/packages/@o3r/extractors/src/core/comparator/package-managers-extractors/npm-file-extractor.helper.ts b/packages/@o3r/extractors/src/core/comparator/package-managers-extractors/npm-file-extractor.helper.ts new file mode 100644 index 0000000000..4c7ceee6d6 --- /dev/null +++ b/packages/@o3r/extractors/src/core/comparator/package-managers-extractors/npm-file-extractor.helper.ts @@ -0,0 +1,45 @@ +import { spawnSync, SpawnSyncOptionsWithBufferEncoding, SpawnSyncReturns } from 'node:child_process'; +import { mkdirSync, readFileSync, rmSync } from 'node:fs'; +import { randomBytes } from 'node:crypto'; +import { tmpdir } from 'node:os'; +import { join, posix, sep } from 'node:path'; + +function runAndThrowOnError(command: string, spawnOptions: SpawnSyncOptionsWithBufferEncoding): SpawnSyncReturns { + const cmdOutput = spawnSync(command, spawnOptions); + if (cmdOutput.error || cmdOutput.status !== 0) { + throw cmdOutput.stderr.toString(); + } + return cmdOutput; +} + +function pathToPosix(path: string): string { + return path.split(sep).join(posix.sep); +} + +/** + * Retrieves the list of given files from an npm package using npm. + * @param packageDescriptor Package descriptor using the npm semver format (i.e. @o3r/demo@^1.2.3) + * @param paths Paths of the files to extract + */ +export function getFilesFromRegistry(packageDescriptor: string, paths: string[]): { [key: string]: string } { + const tempDirName = 'o3r-' + randomBytes(16).toString('hex'); + const tempDirPath = join(tmpdir(), tempDirName); + let extractedFiles: { [key: string]: string } = {}; + mkdirSync(tempDirPath); + + try { + const npmPackCmd = runAndThrowOnError(`npm pack "${packageDescriptor}" --pack-destination ${pathToPosix(tempDirPath)}`, { shell: true }); + const tgzFile = npmPackCmd.stdout.toString().trim(); + + extractedFiles = paths.reduce((filesContent, path) => { + // tar expects a posix path + const pathInTgz = posix.join('./package', path); + runAndThrowOnError(`tar -zxvf ${pathToPosix(tgzFile)} -C ${pathToPosix(tempDirPath)} ${pathInTgz}`, { shell: true, cwd: tempDirPath }); + filesContent[path] = readFileSync(join(tempDirPath, pathInTgz)).toString(); + return filesContent; + }, extractedFiles); + } finally { + rmSync(tempDirPath, { recursive: true }); + } + return extractedFiles; +} diff --git a/packages/@o3r/extractors/src/core/comparator/package-managers-extractors/yarn2-file-extractor.helper.ts b/packages/@o3r/extractors/src/core/comparator/package-managers-extractors/yarn2-file-extractor.helper.ts new file mode 100644 index 0000000000..f1e29a9fd7 --- /dev/null +++ b/packages/@o3r/extractors/src/core/comparator/package-managers-extractors/yarn2-file-extractor.helper.ts @@ -0,0 +1,194 @@ +import { + Cache, + Configuration, + Descriptor, + FetchOptions, + FetchResult, + Locator, + MinimalResolveOptions, + MultiFetcher, + Package, + Project, + ResolveOptions, + Resolver, + structUtils, + ThrowReport +} from '@yarnpkg/core'; +import { npath } from '@yarnpkg/fslib'; +import yarnNpmPlugin from '@yarnpkg/plugin-npm'; +import { join } from 'node:path'; +import { O3rCliError } from '@o3r/schematics'; + +// Class copied from https://github.com/yarnpkg/berry/blob/master/packages/yarnpkg-core/sources/MultiResolver.ts +// because it is not exposed in @yarnpkg/core +export class MultiResolver implements Resolver { + private readonly resolvers: Resolver[]; + + constructor(resolvers: (Resolver | null)[]) { + this.resolvers = resolvers.filter((resolver): resolver is Resolver => !!resolver); + } + + private tryResolverByDescriptor(descriptor: Descriptor, opts: MinimalResolveOptions) { + const resolver = this.resolvers.find((r) => r.supportsDescriptor(descriptor, opts)); + + if (!resolver) { + return null; + } + + return resolver; + } + + private getResolverByDescriptor(descriptor: Descriptor, opts: MinimalResolveOptions) { + const resolver = this.resolvers.find((r) => r.supportsDescriptor(descriptor, opts)); + + if (!resolver) { + throw new Error(`${structUtils.prettyDescriptor(opts.project.configuration, descriptor)} isn't supported by any available resolver`); + } + + return resolver; + } + + private tryResolverByLocator(locator: Locator, opts: MinimalResolveOptions) { + const resolver = this.resolvers.find((r) => r.supportsLocator(locator, opts)); + + if (!resolver) { + return null; + } + + return resolver; + } + + private getResolverByLocator(locator: Locator, opts: MinimalResolveOptions) { + const resolver = this.resolvers.find((r) => r.supportsLocator(locator, opts)); + + if (!resolver) { + throw new Error(`${structUtils.prettyLocator(opts.project.configuration, locator)} isn't supported by any available resolver`); + } + + return resolver; + } + + public supportsDescriptor(descriptor: Descriptor, opts: MinimalResolveOptions) { + const resolver = this.tryResolverByDescriptor(descriptor, opts); + + return !!resolver; + } + + public supportsLocator(locator: Locator, opts: MinimalResolveOptions) { + const resolver = this.tryResolverByLocator(locator, opts); + + return !!resolver; + } + + public shouldPersistResolution(locator: Locator, opts: MinimalResolveOptions) { + const resolver = this.getResolverByLocator(locator, opts); + + return resolver.shouldPersistResolution(locator, opts); + } + + public bindDescriptor(descriptor: Descriptor, fromLocator: Locator, opts: MinimalResolveOptions) { + const resolver = this.getResolverByDescriptor(descriptor, opts); + + return resolver.bindDescriptor(descriptor, fromLocator, opts); + } + + public getResolutionDependencies(descriptor: Descriptor, opts: MinimalResolveOptions) { + const resolver = this.getResolverByDescriptor(descriptor, opts); + + return resolver.getResolutionDependencies(descriptor, opts); + } + + public async getCandidates(descriptor: Descriptor, dependencies: Record, opts: ResolveOptions) { + const resolver = this.getResolverByDescriptor(descriptor, opts); + + return await resolver.getCandidates(descriptor, dependencies, opts); + } + + public async getSatisfying(descriptor: Descriptor, dependencies: Record, locators: Locator[], opts: ResolveOptions) { + const resolver = this.getResolverByDescriptor(descriptor, opts); + + return resolver.getSatisfying(descriptor, dependencies, locators, opts); + } + + public async resolve(locator: Locator, opts: ResolveOptions) { + const resolver = this.getResolverByLocator(locator, opts); + + return await resolver.resolve(locator, opts); + } +} + +// TODO : how can we handle the yarn version discrepancy between user and package v? +// Test with different yarn major versions configured for wrks +async function getProject(cwd = process.cwd()) { + const pluginConfiguration = { + plugins: new Set(['plugin-npm']), + modules: new Map([['plugin-npm', yarnNpmPlugin]]) + }; + const configuration = await Configuration.find(npath.toPortablePath(cwd), pluginConfiguration, { strict: false }); + if (!configuration.projectCwd) { + throw new O3rCliError(`No project found from ${cwd}`); + } + const { project } = await Project.find(configuration, configuration.projectCwd); + return project; +} + +function getDescriptorFromReference(packageReference: string) { + const descriptor = structUtils.tryParseDescriptor(packageReference); + if (!descriptor) { + throw new O3rCliError(`Invalid package descriptor ${packageReference}`); + } + return descriptor; +} + +async function fetchPackage(project: Project, descriptor: Descriptor): Promise { + // eslint-disable-next-line @typescript-eslint/naming-convention + const cache = await Cache.find(project.configuration); + const report = new ThrowReport(); + const multiResolver = new MultiResolver( + // eslint-disable-next-line new-cap + (yarnNpmPlugin.resolvers || []).map((resolver) => new resolver()) + ); + const multiFetcher = new MultiFetcher( + // eslint-disable-next-line new-cap + (yarnNpmPlugin.fetchers || []).map((fetcher) => new fetcher()) + ); + const fetchOptions: FetchOptions = { project, cache, checksums: project.storedChecksums, report, fetcher: multiFetcher }; + const resolveOptions: ResolveOptions = { project, report, resolver: multiResolver, fetchOptions }; + + const normalizedDescriptor = project.configuration.normalizeDependency(descriptor); + const candidate = await multiResolver.getCandidates(normalizedDescriptor, {}, resolveOptions); + + const descriptorStringify = `${descriptor.scope ? '@' + descriptor.scope : ''}/${descriptor.name}@${descriptor.range}`; + if (candidate.length === 0) { + throw new O3rCliError(`No candidate found for ${descriptorStringify}`); + } + const isSupported = multiFetcher.supports(candidate[0], fetchOptions); + if (!isSupported) { + throw new O3rCliError(`Fetcher does not support ${descriptorStringify}`); + } + return multiFetcher.fetch(candidate[0], resolveOptions.fetchOptions!); +} + +/** + * Retrieves the list of given files using yarn + * from an npm package that is not present in the dependencies. + * @param packageDescriptor Package descriptor using the npm semver format (i.e. @o3r/demo@^1.2.3) + * @param paths Paths of the files to extract + * @param cwd working directory + */ +export async function getFilesFromRegistry(packageDescriptor: string, paths: string[], cwd = process.cwd()): Promise<{ [key: string]: string }> { + const project = await getProject(cwd); + const descriptor = getDescriptorFromReference(packageDescriptor); + const result = await fetchPackage(project, descriptor); + const extractedFiles = paths.reduce((acc: Record, path) => { + acc[path] = result.packageFs.readFileSync( + npath.toPortablePath(join(result.prefixPath, path)), + 'utf-8' + ); + return acc; + }, {}); + if (result.releaseFs) { + result.releaseFs(); + } + return extractedFiles; +} diff --git a/packages/@o3r/extractors/src/core/index.ts b/packages/@o3r/extractors/src/core/index.ts index b31a870ea4..24e9ad606b 100644 --- a/packages/@o3r/extractors/src/core/index.ts +++ b/packages/@o3r/extractors/src/core/index.ts @@ -1,3 +1,4 @@ +export * from './comparator'; export * from './converter'; export * from './extractor'; export * from './formats/index'; diff --git a/packages/@o3r/extractors/testing/jest.config.it.js b/packages/@o3r/extractors/testing/jest.config.it.js index 693a6db125..d841bfa637 100644 --- a/packages/@o3r/extractors/testing/jest.config.it.js +++ b/packages/@o3r/extractors/testing/jest.config.it.js @@ -3,6 +3,6 @@ const getJestConfig = require('../../../../jest.config.it').getJestConfig; /** @type {import('ts-jest/dist/types').JestConfigWithTsJest} */ module.exports = { - ...getJestConfig(dirname(__dirname)), + ...getJestConfig(dirname(__dirname), { tsconfig: '/tsconfig.it.spec.json' }), displayName: require('../package.json').name }; diff --git a/packages/@o3r/extractors/tsconfig.it.spec.json b/packages/@o3r/extractors/tsconfig.it.spec.json new file mode 100644 index 0000000000..890bab4955 --- /dev/null +++ b/packages/@o3r/extractors/tsconfig.it.spec.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.spec", + "compilerOptions": { + "esModuleInterop": true + } +} diff --git a/packages/@o3r/extractors/tsconfig.spec.json b/packages/@o3r/extractors/tsconfig.spec.json index 4c678bb0f4..92d7b5ba94 100644 --- a/packages/@o3r/extractors/tsconfig.spec.json +++ b/packages/@o3r/extractors/tsconfig.spec.json @@ -4,6 +4,7 @@ "composite": true, "outDir": "test", "rootDir": ".", + "esModuleInterop": false }, "include": [ "**/*.spec.ts" diff --git a/packages/@o3r/localization/builders.json b/packages/@o3r/localization/builders.json index e42b7019f6..67652c9e45 100644 --- a/packages/@o3r/localization/builders.json +++ b/packages/@o3r/localization/builders.json @@ -15,6 +15,11 @@ "implementation": "./builders/localization/", "schema": "./builders/localization/schema.json", "description": "Generate the localizations" + }, + "check-localization-migration-metadata": { + "implementation": "./builders/metadata-check/", + "schema": "./builders/metadata-check/schema.json", + "description": "Check for localization metadata breaking changes" } } } diff --git a/packages/@o3r/localization/builders/metadata-check/helpers/index.ts b/packages/@o3r/localization/builders/metadata-check/helpers/index.ts new file mode 100644 index 0000000000..7d16340b0e --- /dev/null +++ b/packages/@o3r/localization/builders/metadata-check/helpers/index.ts @@ -0,0 +1 @@ +export * from './localization-metadata-comparison.helper'; diff --git a/packages/@o3r/localization/builders/metadata-check/helpers/localization-metadata-comparison.helper.ts b/packages/@o3r/localization/builders/metadata-check/helpers/localization-metadata-comparison.helper.ts new file mode 100644 index 0000000000..84ae66b3e2 --- /dev/null +++ b/packages/@o3r/localization/builders/metadata-check/helpers/localization-metadata-comparison.helper.ts @@ -0,0 +1,30 @@ +import type { JSONLocalization, LocalizationMetadata } from '@o3r/localization'; +import type { MetadataComparator } from '@o3r/extractors'; + +/** + * Interface describing a localization migration element + */ +export interface MigrationLocalizationMetadata { + /** Localization key */ + key: string; +} + +/** + * Returns an array of localization metadata from a metadata file. + * @param content Content of a migration metadata file + */ +const getLocalizationArray = (content: LocalizationMetadata) => content; + +const getLocalizationName = (localization: JSONLocalization) => localization.key; + +const isMigrationLocalizationDataMatch = (localization: JSONLocalization, migrationData: MigrationLocalizationMetadata) => getLocalizationName(localization) === migrationData.key; + + +/** + * Comparator used to compare one version of localization metadata with another + */ +export const localizationMetadataComparator: MetadataComparator = { + getArray: getLocalizationArray, + getIdentifier: getLocalizationName, + isMigrationDataMatch: isMigrationLocalizationDataMatch +}; diff --git a/packages/@o3r/localization/builders/metadata-check/index.it.spec.ts b/packages/@o3r/localization/builders/metadata-check/index.it.spec.ts new file mode 100644 index 0000000000..55d7dc9b3b --- /dev/null +++ b/packages/@o3r/localization/builders/metadata-check/index.it.spec.ts @@ -0,0 +1,260 @@ +/** + * Test environment exported by O3rEnvironment, must be first line of the file + * @jest-environment @o3r/test-helpers/jest-environment + * @jest-environment-o3r-app-folder test-app-localization-metadata-check + */ +const o3rEnvironment = globalThis.o3rEnvironment; + +import type { MigrationFile } from '@o3r/extractors'; +import { getPackageManager } from '@o3r/schematics'; +import { + getDefaultExecSyncOptions, + getLatestPackageVersion, + packageManagerAdd, + packageManagerExec, + packageManagerPublish, + packageManagerVersion +} from '@o3r/test-helpers'; +import { execFileSync } from 'node:child_process'; +import { promises, readFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { inc } from 'semver'; +import type { JSONLocalization, LocalizationMetadata } from '@o3r/localization'; +import type { MigrationLocalizationMetadata } from './helpers/localization-metadata-comparison.helper'; +import { getExternalDependenciesVersionRange } from '@o3r/schematics'; + +const baseVersion = '1.2.0'; +const version = '1.3.0'; +const migrationDataFileName = `MIGRATION-${version}.json`; +const metadataFileName = 'localisation.metadata.json'; + +const defaultMigrationData: MigrationFile = { + version, + changes: [ + { // Rename key name + 'contentType': 'LOCALIZATION', + 'before': { + 'key': 'localization.key1' + }, + 'after': { + 'key': 'new-localization.key1' + } + } + ] +}; + +const createLoc = (key: string): JSONLocalization => ({ + key, + description: '', + dictionary: false, + referenceData: false +}); + +const previousLocalizationMetadata: LocalizationMetadata = [ + createLoc('localization.key0'), + createLoc('localization.key1') +]; + +const newLocalizationMetadata: LocalizationMetadata = [ + previousLocalizationMetadata[0], + createLoc('new-localization.key1') +]; + +function writeFileAsJSON(path: string, content: object) { + return promises.writeFile(path, JSON.stringify(content), { encoding: 'utf8' }); +} + +const initTest = async ( + allowBreakingChanges: boolean, + newMetadata: LocalizationMetadata, + migrationData: MigrationFile, + packageNameSuffix: string +) => { + const { workspacePath, appName, applicationPath, o3rVersion, isYarnTest } = o3rEnvironment.testEnvironment; + const execAppOptions = { ...getDefaultExecSyncOptions(), cwd: applicationPath }; + const execAppOptionsWorkspace = { ...getDefaultExecSyncOptions(), cwd: workspacePath }; + packageManagerAdd(`@o3r/localization@${o3rVersion}`, execAppOptionsWorkspace); + packageManagerAdd(`@o3r/extractors@${o3rVersion}`, execAppOptionsWorkspace); + const versions = getExternalDependenciesVersionRange([ + 'semver', + ...(isYarnTest ? [ + '@yarnpkg/core', + '@yarnpkg/fslib', + '@yarnpkg/plugin-npm', + '@yarnpkg/plugin-pack', + '@yarnpkg/cli' + ] : []) + ], join(__dirname, '..', '..', 'package.json'), { + warn: jest.fn() + } as any); + Object.entries(versions).forEach(([pkgName, pkgVersion]) => packageManagerAdd(`${pkgName}@${pkgVersion}`, execAppOptionsWorkspace)); + const packageJsonPath = join(applicationPath, 'package.json'); + const angularJsonPath = join(workspacePath, 'angular.json'); + const metadataPath = join(applicationPath, metadataFileName); + const migrationDataPath = join(applicationPath, migrationDataFileName); + + // Add builder options + const angularJson = JSON.parse(readFileSync(angularJsonPath, { encoding: 'utf8' }).toString()); + const builderConfig = { + builder: '@o3r/localization:check-localization-migration-metadata', + options: { + allowBreakingChanges, + migrationDataPath: `**/MIGRATION-*.json` + } + }; + angularJson.projects[appName].architect['check-metadata'] = builderConfig; + await writeFileAsJSON(angularJsonPath, angularJson); + + // Add scope to project for registry management + let packageJson = JSON.parse(readFileSync(packageJsonPath, { encoding: 'utf8' }).toString()); + const packageName = `@o3r/${packageJson.name}-${packageNameSuffix}`; + packageJson = { + ...packageJson, + name: packageName, + private: false + }; + await writeFileAsJSON(packageJsonPath, packageJson); + + // Set old metadata and publish to registry + await writeFileAsJSON(metadataPath, previousLocalizationMetadata); + + let latestVersion; + try { + latestVersion = getLatestPackageVersion(packageName, execAppOptionsWorkspace); + } catch { + latestVersion = baseVersion; + } + + const bumpedVersion = inc(latestVersion, 'patch'); + + const args = getPackageManager() === 'yarn' ? [] : ['--no-git-tag-version', '-f']; + packageManagerVersion(bumpedVersion, args, execAppOptions); + + packageManagerPublish([], execAppOptions); + + // Override with new metadata for comparison + await writeFileAsJSON(metadataPath, newMetadata); + + // Add migration data file + await writeFileAsJSON(migrationDataPath, migrationData); +}; + +describe('check metadata migration', () => { + beforeEach(async () => { + const { applicationPath } = o3rEnvironment.testEnvironment; + const execAppOptions = { ...getDefaultExecSyncOptions(), cwd: applicationPath, shell: true }; + await promises.copyFile( + join(__dirname, '..', '..', '..', '..', '..', '.verdaccio', 'conf', '.npmrc'), + join(applicationPath, '.npmrc') + ); + execFileSync('npx', [ + '--yes', + 'npm-cli-login', + '-u', + 'verdaccio', + '-p', + 'verdaccio', + '-e', + 'test@test.com', + '-r', + 'http://127.0.0.1:4873' + ], execAppOptions); + }); + + test('should not throw', async () => { + await initTest( + true, + newLocalizationMetadata, + defaultMigrationData, + 'allow-breaking-changes' + ); + const { workspacePath, appName } = o3rEnvironment.testEnvironment; + const execAppOptionsWorkspace = { ...getDefaultExecSyncOptions(), cwd: workspacePath }; + + expect(() => packageManagerExec({ script: 'ng', args: ['run', `${appName}:check-metadata`] }, execAppOptionsWorkspace)).not.toThrow(); + }); + + test('should throw because no migration data', async () => { + await initTest( + true, + newLocalizationMetadata, + { + ...defaultMigrationData, + changes: [] + }, + 'no-migration-data' + ); + const { workspacePath, appName } = o3rEnvironment.testEnvironment; + const execAppOptionsWorkspace = { ...getDefaultExecSyncOptions(), cwd: workspacePath }; + + try { + packageManagerExec({ script: 'ng', args: ['run', `${appName}:check-metadata`] }, execAppOptionsWorkspace); + throw new Error('should have thrown before'); + } catch (e: any) { + expect(e.message).not.toBe('should have thrown before'); + previousLocalizationMetadata.slice(1).forEach(({ key: id }) => { + expect(e.message).toContain(`Property ${id} has been modified but is not documented in the migration document`); + expect(e.message).not.toContain(`Property ${id} has been modified but the new property is not present in the new metadata`); + expect(e.message).not.toContain(`Property ${id} is not present in the new metadata and breaking changes are not allowed`); + }); + } + }); + + test('should throw because migration data invalid', async () => { + await initTest( + true, + [newLocalizationMetadata[0]], + { + ...defaultMigrationData, + changes: defaultMigrationData.changes.map((change) => ({ + ...change, + after: { + ...change.after, + key: 'invalid.key' + } + })) + }, + 'invalid-data' + ); + const { workspacePath, appName } = o3rEnvironment.testEnvironment; + const execAppOptionsWorkspace = { ...getDefaultExecSyncOptions(), cwd: workspacePath }; + + try { + packageManagerExec({ script: 'ng', args: ['run', `${appName}:check-metadata`] }, execAppOptionsWorkspace); + throw new Error('should have thrown before'); + } catch (e: any) { + expect(e.message).not.toBe('should have thrown before'); + previousLocalizationMetadata.slice(1).forEach(({ key: id }) => { + expect(e.message).not.toContain(`Property ${id} has been modified but is not documented in the migration document`); + expect(e.message).toContain(`Property ${id} has been modified but the new property is not present in the new metadata`); + expect(e.message).not.toContain(`Property ${id} is not present in the new metadata and breaking changes are not allowed`); + }); + } + }); + + test('should throw because breaking changes are not allowed', async () => { + await initTest( + false, + newLocalizationMetadata, + { + ...defaultMigrationData, + changes: [] + }, + 'breaking-changes' + ); + const { workspacePath, appName } = o3rEnvironment.testEnvironment; + const execAppOptionsWorkspace = { ...getDefaultExecSyncOptions(), cwd: workspacePath }; + + try { + packageManagerExec({ script: 'ng', args: ['run', `${appName}:check-metadata`] }, execAppOptionsWorkspace); + throw new Error('should have thrown before'); + } catch (e: any) { + expect(e.message).not.toBe('should have thrown before'); + previousLocalizationMetadata.slice(1).forEach(({ key: id }) => { + expect(e.message).not.toContain(`Property ${id} has been modified but is not documented in the migration document`); + expect(e.message).not.toContain(`Property ${id} has been modified but the new property is not present in the new metadata`); + expect(e.message).toContain(`Property ${id} is not present in the new metadata and breaking changes are not allowed`); + }); + } + }); +}); diff --git a/packages/@o3r/localization/builders/metadata-check/index.ts b/packages/@o3r/localization/builders/metadata-check/index.ts new file mode 100644 index 0000000000..da4bf24a0e --- /dev/null +++ b/packages/@o3r/localization/builders/metadata-check/index.ts @@ -0,0 +1,8 @@ +import { type BuilderOutput, createBuilder } from '@angular-devkit/architect'; +import { checkMetadataBuilder, createBuilderWithMetricsIfInstalled } from '@o3r/extractors'; +import { localizationMetadataComparator } from './helpers'; +import type { LocalizationMigrationMetadataCheckBuilderSchema } from './schema'; + +export default createBuilder(createBuilderWithMetricsIfInstalled((options, context): Promise => { + return checkMetadataBuilder(options, context, localizationMetadataComparator); +})); diff --git a/packages/@o3r/localization/builders/metadata-check/schema.json b/packages/@o3r/localization/builders/metadata-check/schema.json new file mode 100644 index 0000000000..9b3e20b2ec --- /dev/null +++ b/packages/@o3r/localization/builders/metadata-check/schema.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "type": "object", + "$id": "LocalizationMigrationMetadataCheckBuilderSchema", + "title": "Check localization migration metadata builder", + "description": "Check localization migration metadata builder", + "properties": { + "migrationDataPath": { + "type": ["string", "array"], + "items": { + "type": "string" + }, + "description": "Glob of the migration files to use." + }, + "granularity": { + "type": "string", + "description": "Granularity of the migration check.", + "default": "minor", + "enum": [ + "major", + "minor" + ] + }, + "allowBreakingChanges": { + "type": "boolean", + "description": "Are breaking changes allowed.", + "default": false + }, + "packageManager": { + "type": "string", + "description": "Override of the package manager, otherwise it will be determined from the project." + }, + "metadataPath": { + "type": "string", + "description": "Path of the localization metadata file.", + "default": "./localisation.metadata.json" + } + }, + "additionalProperties": false, + "required": ["migrationDataPath"] +} diff --git a/packages/@o3r/localization/builders/metadata-check/schema.ts b/packages/@o3r/localization/builders/metadata-check/schema.ts new file mode 100644 index 0000000000..945dfa45ab --- /dev/null +++ b/packages/@o3r/localization/builders/metadata-check/schema.ts @@ -0,0 +1,5 @@ +import type { MigrationMetadataCheckBuilderOptions } from '@o3r/extractors'; + +/** Migration metadata check builder schema */ +export interface LocalizationMigrationMetadataCheckBuilderSchema extends MigrationMetadataCheckBuilderOptions { +} diff --git a/packages/@o3r/localization/package.json b/packages/@o3r/localization/package.json index 0f448e4b0e..8f00fe3807 100644 --- a/packages/@o3r/localization/package.json +++ b/packages/@o3r/localization/package.json @@ -48,10 +48,16 @@ "@o3r/logger": "workspace:^", "@o3r/schematics": "workspace:^", "@schematics/angular": "~18.0.0", + "@yarnpkg/cli": "^4.3.1", + "@yarnpkg/core": "^4.1.1", + "@yarnpkg/fslib": "^3.1.0", + "@yarnpkg/plugin-npm": "^3.0.1", + "@yarnpkg/plugin-pack": "^4.0.0", "chokidar": "^3.5.2", "globby": "^11.1.0", "intl-messageformat": "~10.5.0", "rxjs": "^7.8.1", + "semver": "^7.5.2", "typescript": "~5.4.2" }, "peerDependenciesMeta": { @@ -73,12 +79,30 @@ "@schematics/angular": { "optional": true }, + "@yarnpkg/cli": { + "optional": true + }, + "@yarnpkg/core": { + "optional": true + }, + "@yarnpkg/fslib": { + "optional": true + }, + "@yarnpkg/plugin-npm": { + "optional": true + }, + "@yarnpkg/plugin-pack": { + "optional": true + }, "chokidar": { "optional": true }, "globby": { "optional": true }, + "semver": { + "optional": true + }, "typescript": { "optional": true } @@ -123,9 +147,15 @@ "@stylistic/eslint-plugin-ts": "^2.0.0", "@types/jest": "~29.5.2", "@types/node": "^20.0.0", + "@types/semver": "^7.3.13", "@typescript-eslint/eslint-plugin": "^7.14.1", "@typescript-eslint/parser": "^7.14.1", "@typescript-eslint/utils": "^7.14.1", + "@yarnpkg/cli": "^4.3.1", + "@yarnpkg/core": "^4.1.1", + "@yarnpkg/fslib": "^3.1.0", + "@yarnpkg/plugin-npm": "^3.0.1", + "@yarnpkg/plugin-pack": "^4.0.0", "chokidar": "^3.5.2", "cpy-cli": "^5.0.0", "eslint": "^8.57.0", diff --git a/packages/@o3r/localization/schematics/cms-adapter/index.ts b/packages/@o3r/localization/schematics/cms-adapter/index.ts index fa81050e04..09b24f0dda 100644 --- a/packages/@o3r/localization/schematics/cms-adapter/index.ts +++ b/packages/@o3r/localization/schematics/cms-adapter/index.ts @@ -38,6 +38,12 @@ function updateCmsAdapterFn(options: { projectName?: string | undefined }): Rule libraries: [] } }; + workspaceProject.architect['check-localization-migration-metadata'] ||= { + builder: '@o3r/localization:check-localization-migration-metadata', + options: { + migrationDataPath: 'MIGRATION-*.json' + } + }; workspace.projects[options.projectName!] = workspaceProject; tree.overwrite('/angular.json', JSON.stringify(workspace, null, 2)); diff --git a/packages/@o3r/styling/builders.json b/packages/@o3r/styling/builders.json index 3dac4ea250..b8182971e8 100644 --- a/packages/@o3r/styling/builders.json +++ b/packages/@o3r/styling/builders.json @@ -5,6 +5,11 @@ "implementation": "./builders/style-extractor/", "schema": "./builders/style-extractor/schema.json", "description": "Extract the CSS variables from an Otter project" + }, + "check-style-migration-metadata": { + "implementation": "./builders/metadata-check/", + "schema": "./builders/metadata-check/schema.json", + "description": "Check for style metadata breaking changes" } } } diff --git a/packages/@o3r/styling/builders/metadata-check/helpers/index.ts b/packages/@o3r/styling/builders/metadata-check/helpers/index.ts new file mode 100644 index 0000000000..18512106a2 --- /dev/null +++ b/packages/@o3r/styling/builders/metadata-check/helpers/index.ts @@ -0,0 +1 @@ +export * from './styling-metadata-comparison.helper'; diff --git a/packages/@o3r/styling/builders/metadata-check/helpers/styling-metadata-comparison.helper.ts b/packages/@o3r/styling/builders/metadata-check/helpers/styling-metadata-comparison.helper.ts new file mode 100644 index 0000000000..aa2ff2968f --- /dev/null +++ b/packages/@o3r/styling/builders/metadata-check/helpers/styling-metadata-comparison.helper.ts @@ -0,0 +1,28 @@ +import type { CssMetadata, CssVariable } from '@o3r/styling'; +import type { MetadataComparator } from '@o3r/extractors'; + +/** + * Interface describing a style migration element + */ +export interface MigrationStylingData { + /** CSS variable name */ + name: string; +} +/** + * Returns an array of styling metadata from a metadata file. + * @param content Content of a migration metadata file + */ +const getCssVariablesArray = (content: CssMetadata): CssVariable[] => Object.values(content.variables); + +const getCssVariableName = (cssVariable: CssVariable) => cssVariable.name; + +const isMigrationCssVariableDataMatch = (cssVariable: CssVariable, migrationData: MigrationStylingData) => getCssVariableName(cssVariable) === migrationData.name; + +/** + * Comparator used to compare one version of styling metadata with another + */ +export const stylingMetadataComparator: MetadataComparator = { + getArray: getCssVariablesArray, + getIdentifier: getCssVariableName, + isMigrationDataMatch: isMigrationCssVariableDataMatch +}; diff --git a/packages/@o3r/styling/builders/metadata-check/index.it.spec.ts b/packages/@o3r/styling/builders/metadata-check/index.it.spec.ts new file mode 100644 index 0000000000..441c7f898c --- /dev/null +++ b/packages/@o3r/styling/builders/metadata-check/index.it.spec.ts @@ -0,0 +1,272 @@ +/** + * Test environment exported by O3rEnvironment, must be first line of the file + * @jest-environment @o3r/test-helpers/jest-environment + * @jest-environment-o3r-app-folder test-app-styling-metadata-check + */ +const o3rEnvironment = globalThis.o3rEnvironment; + +import type { MigrationFile } from '@o3r/extractors'; +import { getPackageManager } from '@o3r/schematics'; +import { + getDefaultExecSyncOptions, + getLatestPackageVersion, + packageManagerAdd, + packageManagerExec, + packageManagerPublish, + packageManagerVersion +} from '@o3r/test-helpers'; +import { execFileSync } from 'node:child_process'; +import { promises, readFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { inc } from 'semver'; +import type { CssMetadata, CssVariable } from '@o3r/styling'; +import type { MigrationStylingData } from './helpers/styling-metadata-comparison.helper'; +import { getExternalDependenciesVersionRange } from '@o3r/schematics'; + +const baseVersion = '1.2.0'; +const version = '1.3.0'; +const migrationDataFileName = `MIGRATION-${version}.json`; +const metadataFileName = 'style.metadata.json'; + +const defaultMigrationData: MigrationFile = { + version, + changes: [ + { // Rename key name + 'contentType': 'STYLE', + 'before': { + 'name': 'css-var-name1' + }, + 'after': { + 'name': 'new-css-var-name1' + } + } + ] +}; + +const createCssVar = (name: string): CssVariable => ({ + name, + defaultValue: '#fff' +}); + +const unchangedVariableName = 'css-var-name0'; + +/* eslint-disable @typescript-eslint/naming-convention */ +const previousStylingMetadata: CssMetadata = { + variables: { + [unchangedVariableName]: createCssVar(unchangedVariableName), + 'css-var-name1': createCssVar('css-var-name1') + } +}; + +const newStylingMetadata: CssMetadata = { + ...previousStylingMetadata, + variables: { + [unchangedVariableName]: previousStylingMetadata.variables[unchangedVariableName], + 'new-css-var-name1': createCssVar('new-css-var-name1') + } +}; +/* eslint-enable @typescript-eslint/naming-convention */ + + +function writeFileAsJSON(path: string, content: object) { + return promises.writeFile(path, JSON.stringify(content), { encoding: 'utf8' }); +} + +const initTest = async ( + allowBreakingChanges: boolean, + newMetadata: CssMetadata, + migrationData: MigrationFile, + packageNameSuffix: string +) => { + const { workspacePath, appName, applicationPath, o3rVersion, isYarnTest } = o3rEnvironment.testEnvironment; + const execAppOptions = { ...getDefaultExecSyncOptions(), cwd: applicationPath }; + const execAppOptionsWorkspace = { ...getDefaultExecSyncOptions(), cwd: workspacePath }; + packageManagerAdd(`@o3r/styling@${o3rVersion}`, execAppOptionsWorkspace); + packageManagerAdd(`@o3r/extractors@${o3rVersion}`, execAppOptionsWorkspace); + const versions = getExternalDependenciesVersionRange([ + 'semver', + ...(isYarnTest ? [ + '@yarnpkg/core', + '@yarnpkg/fslib', + '@yarnpkg/plugin-npm', + '@yarnpkg/plugin-pack', + '@yarnpkg/cli' + ] : []) + ], join(__dirname, '..', '..', 'package.json'), { + warn: jest.fn() + } as any); + Object.entries(versions).forEach(([pkgName, pkgVersion]) => packageManagerAdd(`${pkgName}@${pkgVersion}`, execAppOptionsWorkspace)); + const packageJsonPath = join(applicationPath, 'package.json'); + const angularJsonPath = join(workspacePath, 'angular.json'); + const metadataPath = join(applicationPath, metadataFileName); + const migrationDataPath = join(applicationPath, migrationDataFileName); + + // Add builder options + const angularJson = JSON.parse(readFileSync(angularJsonPath, { encoding: 'utf8' }).toString()); + const builderConfig = { + builder: '@o3r/styling:check-style-migration-metadata', + options: { + allowBreakingChanges, + migrationDataPath: `**/MIGRATION-*.json` + } + }; + angularJson.projects[appName].architect['check-metadata'] = builderConfig; + await writeFileAsJSON(angularJsonPath, angularJson); + + // Add scope to project for registry management + let packageJson = JSON.parse(readFileSync(packageJsonPath, { encoding: 'utf8' }).toString()); + const packageName = `@o3r/${packageJson.name}-${packageNameSuffix}`; + packageJson = { + ...packageJson, + name: packageName, + private: false + }; + await writeFileAsJSON(packageJsonPath, packageJson); + + // Set old metadata and publish to registry + await writeFileAsJSON(metadataPath, previousStylingMetadata); + + let latestVersion; + try { + latestVersion = getLatestPackageVersion(packageName, execAppOptionsWorkspace); + } catch { + latestVersion = baseVersion; + } + + const bumpedVersion = inc(latestVersion, 'patch'); + + const args = getPackageManager() === 'yarn' ? [] : ['--no-git-tag-version', '-f']; + packageManagerVersion(bumpedVersion, args, execAppOptions); + + packageManagerPublish([], execAppOptions); + + // Override with new metadata for comparison + await writeFileAsJSON(metadataPath, newMetadata); + + // Add migration data file + await writeFileAsJSON(migrationDataPath, migrationData); +}; + +describe('check metadata migration', () => { + beforeEach(async () => { + const { applicationPath } = o3rEnvironment.testEnvironment; + const execAppOptions = { ...getDefaultExecSyncOptions(), cwd: applicationPath, shell: true }; + await promises.copyFile( + join(__dirname, '..', '..', '..', '..', '..', '.verdaccio', 'conf', '.npmrc'), + join(applicationPath, '.npmrc') + ); + execFileSync('npx', [ + '--yes', + 'npm-cli-login', + '-u', + 'verdaccio', + '-p', + 'verdaccio', + '-e', + 'test@test.com', + '-r', + 'http://127.0.0.1:4873' + ], execAppOptions); + }); + + test('should not throw', async () => { + await initTest( + true, + newStylingMetadata, + defaultMigrationData, + 'allow-breaking-changes' + ); + const { workspacePath, appName } = o3rEnvironment.testEnvironment; + const execAppOptionsWorkspace = { ...getDefaultExecSyncOptions(), cwd: workspacePath }; + + expect(() => packageManagerExec({ script: 'ng', args: ['run', `${appName}:check-metadata`] }, execAppOptionsWorkspace)).not.toThrow(); + }); + + test('should throw because no migration data', async () => { + await initTest( + true, + newStylingMetadata, + { + ...defaultMigrationData, + changes: [] + }, + 'no-migration-data' + ); + const { workspacePath, appName } = o3rEnvironment.testEnvironment; + const execAppOptionsWorkspace = { ...getDefaultExecSyncOptions(), cwd: workspacePath }; + + try { + packageManagerExec({ script: 'ng', args: ['run', `${appName}:check-metadata`] }, execAppOptionsWorkspace); + throw new Error('should have thrown before'); + } catch (e: any) { + expect(e.message).not.toBe('should have thrown before'); + Object.values(previousStylingMetadata.variables).slice(1).forEach(({ name: id }) => { + expect(e.message).toContain(`Property ${id} has been modified but is not documented in the migration document`); + expect(e.message).not.toContain(`Property ${id} has been modified but the new property is not present in the new metadata`); + expect(e.message).not.toContain(`Property ${id} is not present in the new metadata and breaking changes are not allowed`); + }); + } + }); + + test('should throw because migration data invalid', async () => { + await initTest( + true, + { + variables: { + unchangedVariableName: newStylingMetadata.variables[unchangedVariableName] + } + }, + { + ...defaultMigrationData, + changes: defaultMigrationData.changes.map((change) => ({ + ...change, + after: { + ...change.after, + name: 'invalid-name' + } + })) + }, + 'invalid-data' + ); + const { workspacePath, appName } = o3rEnvironment.testEnvironment; + const execAppOptionsWorkspace = { ...getDefaultExecSyncOptions(), cwd: workspacePath }; + + try { + packageManagerExec({ script: 'ng', args: ['run', `${appName}:check-metadata`] }, execAppOptionsWorkspace); + throw new Error('should have thrown before'); + } catch (e: any) { + expect(e.message).not.toBe('should have thrown before'); + Object.values(previousStylingMetadata.variables).slice(1).forEach(({ name: id }) => { + expect(e.message).not.toContain(`Property ${id} has been modified but is not documented in the migration document`); + expect(e.message).toContain(`Property ${id} has been modified but the new property is not present in the new metadata`); + expect(e.message).not.toContain(`Property ${id} is not present in the new metadata and breaking changes are not allowed`); + }); + } + }); + + test('should throw because breaking changes are not allowed', async () => { + await initTest( + false, + newStylingMetadata, + { + ...defaultMigrationData, + changes: [] + }, + 'breaking-changes' + ); + const { workspacePath, appName } = o3rEnvironment.testEnvironment; + const execAppOptionsWorkspace = { ...getDefaultExecSyncOptions(), cwd: workspacePath }; + + try { + packageManagerExec({ script: 'ng', args: ['run', `${appName}:check-metadata`] }, execAppOptionsWorkspace); + throw new Error('should have thrown before'); + } catch (e: any) { + expect(e.message).not.toBe('should have thrown before'); + Object.values(previousStylingMetadata.variables).slice(1).forEach(({ name: id }) => { + expect(e.message).not.toContain(`Property ${id} has been modified but is not documented in the migration document`); + expect(e.message).not.toContain(`Property ${id} has been modified but the new property is not present in the new metadata`); + expect(e.message).toContain(`Property ${id} is not present in the new metadata and breaking changes are not allowed`); + }); + } + }); +}); diff --git a/packages/@o3r/styling/builders/metadata-check/index.ts b/packages/@o3r/styling/builders/metadata-check/index.ts new file mode 100644 index 0000000000..69d5a4a329 --- /dev/null +++ b/packages/@o3r/styling/builders/metadata-check/index.ts @@ -0,0 +1,8 @@ +import { type BuilderOutput, createBuilder } from '@angular-devkit/architect'; +import { checkMetadataBuilder, createBuilderWithMetricsIfInstalled } from '@o3r/extractors'; +import { stylingMetadataComparator } from './helpers'; +import type { StylingMigrationMetadataCheckBuilderSchema } from './schema'; + +export default createBuilder(createBuilderWithMetricsIfInstalled((options, context): Promise => { + return checkMetadataBuilder(options, context, stylingMetadataComparator); +})); diff --git a/packages/@o3r/styling/builders/metadata-check/schema.json b/packages/@o3r/styling/builders/metadata-check/schema.json new file mode 100644 index 0000000000..1b8879d60d --- /dev/null +++ b/packages/@o3r/styling/builders/metadata-check/schema.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "type": "object", + "$id": "StylingMigrationMetadataCheckBuilderSchema", + "title": "Check styling migration metadata builder", + "description": "Check styling migration metadata builder", + "properties": { + "migrationDataPath": { + "type": ["string", "array"], + "items": { + "type": "string" + }, + "description": "Glob of the migration files to use." + }, + "granularity": { + "type": "string", + "description": "Granularity of the migration check.", + "default": "minor", + "enum": [ + "major", + "minor" + ] + }, + "allowBreakingChanges": { + "type": "boolean", + "description": "Are breaking changes allowed.", + "default": false + }, + "packageManager": { + "type": "string", + "description": "Override of the package manager, otherwise it will be determined from the project." + }, + "metadataPath": { + "type": "string", + "description": "Path of the styling metadata file.", + "default": "./style.metadata.json" + } + }, + "additionalProperties": false, + "required": ["migrationDataPath"] +} diff --git a/packages/@o3r/styling/builders/metadata-check/schema.ts b/packages/@o3r/styling/builders/metadata-check/schema.ts new file mode 100644 index 0000000000..5575c5298e --- /dev/null +++ b/packages/@o3r/styling/builders/metadata-check/schema.ts @@ -0,0 +1,5 @@ +import type { MigrationMetadataCheckBuilderOptions } from '@o3r/extractors'; + +/** Migration metadata check builder schema */ +export interface StylingMigrationMetadataCheckBuilderSchema extends MigrationMetadataCheckBuilderOptions { +} diff --git a/packages/@o3r/styling/package.json b/packages/@o3r/styling/package.json index 6efa8c8ddf..4abd4f6178 100644 --- a/packages/@o3r/styling/package.json +++ b/packages/@o3r/styling/package.json @@ -49,8 +49,14 @@ "@o3r/logger": "workspace:^", "@o3r/schematics": "workspace:^", "@schematics/angular": "~18.0.0", + "@yarnpkg/cli": "^4.3.1", + "@yarnpkg/core": "^4.1.1", + "@yarnpkg/fslib": "^3.1.0", + "@yarnpkg/plugin-npm": "^3.0.1", + "@yarnpkg/plugin-pack": "^4.0.0", "rxjs": "^7.8.1", - "sass": "~1.77.0" + "sass": "~1.77.0", + "semver": "^7.5.2" }, "peerDependenciesMeta": { "@angular-devkit/architect": { @@ -86,8 +92,26 @@ "@schematics/angular": { "optional": true }, + "@yarnpkg/cli": { + "optional": true + }, + "@yarnpkg/core": { + "optional": true + }, + "@yarnpkg/fslib": { + "optional": true + }, + "@yarnpkg/plugin-npm": { + "optional": true + }, + "@yarnpkg/plugin-pack": { + "optional": true + }, "sass": { "optional": true + }, + "semver": { + "optional": true } }, "dependencies": { @@ -128,9 +152,15 @@ "@stylistic/eslint-plugin-ts": "^2.0.0", "@types/jest": "~29.5.2", "@types/node": "^20.0.0", + "@types/semver": "^7.3.13", "@typescript-eslint/eslint-plugin": "^7.14.1", "@typescript-eslint/parser": "^7.14.1", "@typescript-eslint/utils": "^7.14.1", + "@yarnpkg/cli": "^4.3.1", + "@yarnpkg/core": "^4.1.1", + "@yarnpkg/fslib": "^3.1.0", + "@yarnpkg/plugin-npm": "^3.0.1", + "@yarnpkg/plugin-pack": "^4.0.0", "chokidar": "^3.5.2", "cpy-cli": "^5.0.0", "eslint": "^8.57.0", diff --git a/packages/@o3r/styling/schematics/cms-adapter/index.ts b/packages/@o3r/styling/schematics/cms-adapter/index.ts index e778b1fd50..86c9cc4bf5 100644 --- a/packages/@o3r/styling/schematics/cms-adapter/index.ts +++ b/packages/@o3r/styling/schematics/cms-adapter/index.ts @@ -40,6 +40,12 @@ function updateCmsAdapterFn(options: { projectName?: string | undefined }): Rule outputFile: path.join(workspaceProject?.root || '', './style.metadata.json') } }; + workspaceProject.architect['check-style-migration-metadata'] ||= { + builder: '@o3r/styling:check-style-migration-metadata', + options: { + migrationDataPath: 'MIGRATION-*.json' + } + }; workspace.projects[options.projectName!] = workspaceProject; tree.overwrite('/angular.json', JSON.stringify(workspace, null, 2)); diff --git a/packages/@o3r/test-helpers/src/prepare-test-env.ts b/packages/@o3r/test-helpers/src/prepare-test-env.ts index 26b160dcce..503e21755d 100644 --- a/packages/@o3r/test-helpers/src/prepare-test-env.ts +++ b/packages/@o3r/test-helpers/src/prepare-test-env.ts @@ -1,11 +1,11 @@ -import { execFileSync, ExecSyncOptions } from 'node:child_process'; +import { O3rCliError } from '@o3r/schematics'; +import type { ExecSyncOptions } from 'node:child_process'; import { cpSync, existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs'; import * as path from 'node:path'; import type { PackageJson } from 'type-fest'; import { createTestEnvironmentBlank } from './test-environments/create-test-environment-blank'; -import { createWithLock, getPackageManager, type Logger, packageManagerInstall, setPackagerManagerConfig, setupGit } from './utilities/index'; import { createTestEnvironmentOtterProjectWithAppAndLib } from './test-environments/create-test-environment-otter-project'; -import { O3rCliError } from '@o3r/schematics'; +import { createWithLock, getLatestPackageVersion, getPackageManager, type Logger, packageManagerInstall, setPackagerManagerConfig, setupGit } from './utilities/index'; /** * - 'blank' only create yarn/npm config @@ -73,13 +73,7 @@ export async function prepareTestEnv(folderName: string, options?: PrepareTestEn return Promise.resolve(); }, {lockFilePath: `${itTestsFolderPath}.lock`, cwd: path.join(rootFolderPath, '..'), appDirectory: 'it-tests'}); } - const o3rExactVersion = execFileSync('npm', ['info', '@o3r/create', 'version'], { - ...execAppOptions, - cwd: itTestsFolderPath, - stdio: 'pipe', - encoding: 'utf8', - shell: true - }).replace(/\s/g, ''); + const o3rExactVersion = getLatestPackageVersion('@o3r/create', { ...execAppOptions, cwd: itTestsFolderPath }).replace(/\s/g, ''); // Remove existing app if (existsSync(workspacePath)) { diff --git a/packages/@o3r/test-helpers/src/utilities/package-manager.ts b/packages/@o3r/test-helpers/src/utilities/package-manager.ts index 7602a2d2c2..e62559e245 100644 --- a/packages/@o3r/test-helpers/src/utilities/package-manager.ts +++ b/packages/@o3r/test-helpers/src/utilities/package-manager.ts @@ -2,6 +2,7 @@ import { execFileSync, ExecSyncOptions } from 'node:child_process'; import { existsSync, rmSync } from 'node:fs'; import { join } from 'node:path'; import { performance } from 'node:perf_hooks'; +import { type SupportedPackageManagers } from '@o3r/schematics'; declare global { namespace NodeJS { @@ -12,13 +13,28 @@ declare global { } } -const PACKAGE_MANAGERS_CMD = { +type Command = + | 'add' + | 'create' + | 'exec' + | 'info' + | 'install' + | 'publish' + | 'run' + | 'version' + | 'workspaceExec' + | 'workspaceRun'; + +const PACKAGE_MANAGERS_CMD: {[packageManager in SupportedPackageManagers]: {[command in Command]: string[]}} = { npm: { add: ['npm', 'install'], create: ['npm', 'create'], exec: ['npm', 'exec'], + info: ['npm', 'info'], install: ['npm', 'install'], + publish: ['npm', 'publish'], run: ['npm', 'run'], + version: ['npm', 'version'], workspaceExec: ['npm', 'exec', '--workspace'], workspaceRun: ['npm', 'run', '--workspace'] }, @@ -26,8 +42,11 @@ const PACKAGE_MANAGERS_CMD = { add: ['yarn', 'add'], create: ['yarn', 'create'], exec: ['yarn'], + info: ['yarn', 'info'], install: ['yarn', 'install'], + publish: ['npm', 'publish'], // We always use npm publish run: ['yarn', 'run'], + version: ['yarn', 'version'], workspaceExec: ['yarn', 'workspace'], workspaceRun: ['yarn', 'workspace'] } @@ -105,6 +124,35 @@ export function packageManagerCreate(command: CommandArguments, options: ExecSyn return execCmd([...PACKAGE_MANAGERS_CMD[packageManager].create, script, ...addDashesForNpmCommand(args, packageManager)], options); } +/** + * Get information about a package from npm + * @param packageRef + * @param args + * @param options + */ +export function packageManagerInfo(packageRef: string, args: string[], options: ExecSyncOptions) { + return execCmd([...PACKAGE_MANAGERS_CMD[getPackageManager()].info, ...args, packageRef], options); +} + +/** + * Apply a new version to a package + * @param version + * @param args + * @param options + */ +export function packageManagerVersion(version: string, args: string[], options: ExecSyncOptions) { + return execCmd([...PACKAGE_MANAGERS_CMD[getPackageManager()].version, ...args, version], options); +} + +/** + * Publish a package to the npm registry + * @param version + * @param options + */ +export function packageManagerPublish(args: string[], options: ExecSyncOptions) { + return execCmd([...PACKAGE_MANAGERS_CMD[getPackageManager()].publish, ...args], options); +} + /** * Execute a binary command (npx / yarn exec) * @param command @@ -248,3 +296,17 @@ export function setPackagerManagerConfig(options: PackageManagerConfig, execAppO rmSync(packageJsonPath); } } + +/** + * Get the latest version of a package + * @param packageName + * @param execAppOptions + */ +export function getLatestPackageVersion(packageName: string, execAppOptions?: Partial) { + return execFileSync('npm', ['info', packageName, 'version'], { + ...execAppOptions, + stdio: 'pipe', + encoding: 'utf8', + shell: true + }).replace(/\s/g, ''); +} diff --git a/yarn.lock b/yarn.lock index 2fa907b075..0e9bbb6579 100644 --- a/yarn.lock +++ b/yarn.lock @@ -113,6 +113,157 @@ __metadata: languageName: node linkType: hard +"@algolia/cache-browser-local-storage@npm:4.24.0": + version: 4.24.0 + resolution: "@algolia/cache-browser-local-storage@npm:4.24.0" + dependencies: + "@algolia/cache-common": "npm:4.24.0" + checksum: 10/f7f9bdb1fa37e788a5cb8c835e526caff2fa097f68736accd4c82ade5e5cb7f5bbd361cf8fc8c2a4628d979d81bd90597bdaed77ca72de8423593067b3d15040 + languageName: node + linkType: hard + +"@algolia/cache-common@npm:4.24.0": + version: 4.24.0 + resolution: "@algolia/cache-common@npm:4.24.0" + checksum: 10/bc1d0f8731713f7e6f10cd397b7d8f7464f14a2f4e1decc73a48e99ecbc0fe41bd4df1cc3eb0a4ecf286095e3eb3935b2ea40179de98e11676f8e7d78c622df8 + languageName: node + linkType: hard + +"@algolia/cache-in-memory@npm:4.24.0": + version: 4.24.0 + resolution: "@algolia/cache-in-memory@npm:4.24.0" + dependencies: + "@algolia/cache-common": "npm:4.24.0" + checksum: 10/0476f65f4b622b1b38f050a03b9bf02cf6cc77fc69ec785d16e244770eb2c5eea581b089a346d24bdbc3561be78d383f2a8b81179b801b2af72d9795bc48fee2 + languageName: node + linkType: hard + +"@algolia/client-account@npm:4.24.0": + version: 4.24.0 + resolution: "@algolia/client-account@npm:4.24.0" + dependencies: + "@algolia/client-common": "npm:4.24.0" + "@algolia/client-search": "npm:4.24.0" + "@algolia/transporter": "npm:4.24.0" + checksum: 10/059cf39f3e48b2e77a26435267284d2d15a7a3c4e904feb2b2ad2dd207a3ca2e2b3597847ec9f3b1141749b25fb2e6091e9933f53cb86ab278b5b93836c85aad + languageName: node + linkType: hard + +"@algolia/client-analytics@npm:4.24.0": + version: 4.24.0 + resolution: "@algolia/client-analytics@npm:4.24.0" + dependencies: + "@algolia/client-common": "npm:4.24.0" + "@algolia/client-search": "npm:4.24.0" + "@algolia/requester-common": "npm:4.24.0" + "@algolia/transporter": "npm:4.24.0" + checksum: 10/eaa4be80636082a1fbeb0d099ef882ae6576fb0b6dc64988e9e6939533b4ddfffdbe16061cfd3f89b18bbf5aba21dff5a68af4f20b2719cf72d83a1f0774f6d5 + languageName: node + linkType: hard + +"@algolia/client-common@npm:4.24.0": + version: 4.24.0 + resolution: "@algolia/client-common@npm:4.24.0" + dependencies: + "@algolia/requester-common": "npm:4.24.0" + "@algolia/transporter": "npm:4.24.0" + checksum: 10/0271dc8d7b7008f28df612f14790a50a2297bdaac363be28b6261d2ec3ec343c06cc14f3f113d511a2eb4cda49ee4c204e37fc413c9f699234d8e5741b04c98f + languageName: node + linkType: hard + +"@algolia/client-personalization@npm:4.24.0": + version: 4.24.0 + resolution: "@algolia/client-personalization@npm:4.24.0" + dependencies: + "@algolia/client-common": "npm:4.24.0" + "@algolia/requester-common": "npm:4.24.0" + "@algolia/transporter": "npm:4.24.0" + checksum: 10/5b922d547a31ef76cc6872de9b880ac7f5783321d441fd8d596eab57554c882183e1a24b050f411dee0235c7a99bf52393c3937e08db0a7f2c238a8c37985464 + languageName: node + linkType: hard + +"@algolia/client-search@npm:4.24.0": + version: 4.24.0 + resolution: "@algolia/client-search@npm:4.24.0" + dependencies: + "@algolia/client-common": "npm:4.24.0" + "@algolia/requester-common": "npm:4.24.0" + "@algolia/transporter": "npm:4.24.0" + checksum: 10/2cdcc4239b1bd84e3bd642e380d9135612b80dc68393d23211088141d7c8cb055394588babdf5c984817b997e9e0c4356cd50a8a56dd1ee6ad594f5f76c44acb + languageName: node + linkType: hard + +"@algolia/logger-common@npm:4.24.0": + version: 4.24.0 + resolution: "@algolia/logger-common@npm:4.24.0" + checksum: 10/668fb5a2cbb6aaea7648ae522b5d088241589a9da9f8abb53e2daa89ca2d0bc04307291f57c65de7a332e092cc054cc98cc21b12af81620099632ca85c4ef074 + languageName: node + linkType: hard + +"@algolia/logger-console@npm:4.24.0": + version: 4.24.0 + resolution: "@algolia/logger-console@npm:4.24.0" + dependencies: + "@algolia/logger-common": "npm:4.24.0" + checksum: 10/846d94ecac2e914a2aa7d1ace301cca7371b2bc757c737405eca8d29fc1a26e788387862851c90f611c90f43755367ce676802a21fa37a3bf8531b1a16f5183b + languageName: node + linkType: hard + +"@algolia/recommend@npm:4.24.0": + version: 4.24.0 + resolution: "@algolia/recommend@npm:4.24.0" + dependencies: + "@algolia/cache-browser-local-storage": "npm:4.24.0" + "@algolia/cache-common": "npm:4.24.0" + "@algolia/cache-in-memory": "npm:4.24.0" + "@algolia/client-common": "npm:4.24.0" + "@algolia/client-search": "npm:4.24.0" + "@algolia/logger-common": "npm:4.24.0" + "@algolia/logger-console": "npm:4.24.0" + "@algolia/requester-browser-xhr": "npm:4.24.0" + "@algolia/requester-common": "npm:4.24.0" + "@algolia/requester-node-http": "npm:4.24.0" + "@algolia/transporter": "npm:4.24.0" + checksum: 10/cd228381744ddc4547f1796e38e72e52b158823313dcdfde20d99c2510b6c76996bff98e7223e983768c2a13a3c019e65939741429c0f7de19651f98f74bd834 + languageName: node + linkType: hard + +"@algolia/requester-browser-xhr@npm:4.24.0": + version: 4.24.0 + resolution: "@algolia/requester-browser-xhr@npm:4.24.0" + dependencies: + "@algolia/requester-common": "npm:4.24.0" + checksum: 10/7c32d38d6c7a83357f52134f50271f1ee3df63888b28bc53040a3c74ef73458d80efaf44a5943a3769e84737c2ffd0743e1044a3b5e99ce69289f63e22b50f2a + languageName: node + linkType: hard + +"@algolia/requester-common@npm:4.24.0": + version: 4.24.0 + resolution: "@algolia/requester-common@npm:4.24.0" + checksum: 10/5ca1abd00918ad2c9aed379208d920883c7c3e69b480afe0b1d00b4eb205e39ccd347809b368ba764889261f659c85963f9a00d3da3bd59592db74108d54788b + languageName: node + linkType: hard + +"@algolia/requester-node-http@npm:4.24.0": + version: 4.24.0 + resolution: "@algolia/requester-node-http@npm:4.24.0" + dependencies: + "@algolia/requester-common": "npm:4.24.0" + checksum: 10/387ee892bf35f46be269996de88f9ea12841796aa33cb5088ba6460a48733614a33300ee44bca0af22b6fded05c16ec92631fb998e9a7e1e6a30504d8b407c23 + languageName: node + linkType: hard + +"@algolia/transporter@npm:4.24.0": + version: 4.24.0 + resolution: "@algolia/transporter@npm:4.24.0" + dependencies: + "@algolia/cache-common": "npm:4.24.0" + "@algolia/logger-common": "npm:4.24.0" + "@algolia/requester-common": "npm:4.24.0" + checksum: 10/decf4d5da37d62ff720e25313a473160c2be4c83bfb048d5caebea0320f42681138e91e78b359b8f825059c2acc83054bc17d53584701984f5e79822eb770efa + languageName: node + linkType: hard + "@ama-sdk/core@workspace:*, @ama-sdk/core@workspace:^, @ama-sdk/core@workspace:packages/@ama-sdk/core": version: 0.0.0-use.local resolution: "@ama-sdk/core@workspace:packages/@ama-sdk/core" @@ -7327,9 +7478,15 @@ __metadata: "@stylistic/eslint-plugin-ts": "npm:^2.0.0" "@types/jest": "npm:~29.5.2" "@types/node": "npm:^20.0.0" + "@types/semver": "npm:^7.3.13" "@typescript-eslint/eslint-plugin": "npm:^7.14.1" "@typescript-eslint/parser": "npm:^7.14.1" "@typescript-eslint/utils": "npm:^7.14.1" + "@yarnpkg/cli": "npm:^4.3.1" + "@yarnpkg/core": "npm:^4.1.1" + "@yarnpkg/fslib": "npm:^3.1.0" + "@yarnpkg/plugin-npm": "npm:^3.0.1" + "@yarnpkg/plugin-pack": "npm:^4.0.0" chokidar: "npm:^3.5.2" cpy-cli: "npm:^5.0.0" eslint: "npm:^8.57.0" @@ -7353,6 +7510,7 @@ __metadata: ts-jest: "npm:~29.2.0" ts-node: "npm:~10.9.2" tslib: "npm:^2.6.2" + type-fest: "npm:^4.10.2" typescript: "npm:~5.4.2" unionfs: "npm:~4.5.1" zone.js: "npm:~0.14.2" @@ -7379,10 +7537,16 @@ __metadata: "@o3r/schematics": "workspace:^" "@o3r/testing": "workspace:^" "@schematics/angular": ~18.0.0 + "@yarnpkg/cli": ^4.0.0 + "@yarnpkg/core": ^4.1.1 + "@yarnpkg/fslib": ^3.1.0 + "@yarnpkg/plugin-npm": ^3.0.1 + "@yarnpkg/plugin-pack": ^4.0.0 chokidar: ^3.5.2 globby: ^11.1.0 jsonpath-plus: ^9.0.0 rxjs: ^7.8.1 + semver: ^7.5.2 typescript: ~5.4.2 peerDependenciesMeta: "@angular-devkit/schematics": @@ -7405,12 +7569,24 @@ __metadata: optional: true "@schematics/angular": optional: true + "@yarnpkg/cli": + optional: true + "@yarnpkg/core": + optional: true + "@yarnpkg/fslib": + optional: true + "@yarnpkg/plugin-npm": + optional: true + "@yarnpkg/plugin-pack": + optional: true chokidar: optional: true globby: optional: true jsonpath-plus: optional: true + semver: + optional: true typescript: optional: true languageName: unknown @@ -8109,9 +8285,15 @@ __metadata: "@types/inquirer": "npm:~8.2.10" "@types/jest": "npm:~29.5.2" "@types/node": "npm:^20.0.0" + "@types/semver": "npm:^7.3.13" "@typescript-eslint/eslint-plugin": "npm:^7.14.1" "@typescript-eslint/parser": "npm:^7.14.1" "@typescript-eslint/utils": "npm:^7.14.1" + "@yarnpkg/cli": "npm:^4.3.1" + "@yarnpkg/core": "npm:^4.1.1" + "@yarnpkg/fslib": "npm:^3.1.0" + "@yarnpkg/plugin-npm": "npm:^3.0.1" + "@yarnpkg/plugin-pack": "npm:^4.0.0" cpy-cli: "npm:^5.0.0" eslint: "npm:^8.57.0" eslint-import-resolver-node: "npm:^0.3.9" @@ -8119,6 +8301,7 @@ __metadata: eslint-plugin-jsdoc: "npm:~48.7.0" eslint-plugin-prefer-arrow: "npm:~1.2.3" eslint-plugin-unicorn: "npm:^54.0.0" + globby: "npm:^11.1.0" inquirer: "npm:~8.2.6" intl-messageformat: "npm:~10.5.1" jest: "npm:~29.7.0" @@ -8141,17 +8324,42 @@ __metadata: typescript-json-schema: "npm:~0.64.0" zone.js: "npm:~0.14.2" peerDependencies: + "@angular-devkit/architect": ~0.1800.0 "@angular-devkit/core": ~18.0.0 "@o3r/core": "workspace:^" "@o3r/schematics": "workspace:^" "@o3r/telemetry": "workspace:^" "@schematics/angular": ~18.0.0 + "@yarnpkg/cli": ^4.0.0 + "@yarnpkg/core": ^4.1.1 + "@yarnpkg/fslib": ^3.1.0 + "@yarnpkg/plugin-npm": ^3.0.1 + "@yarnpkg/plugin-pack": ^4.0.0 + globby: ^11.1.0 + semver: ^7.5.2 + type-fest: ^4.10.2 typescript: ~5.4.2 peerDependenciesMeta: "@o3r/telemetry": optional: true "@schematics/angular": optional: true + "@yarnpkg/cli": + optional: true + "@yarnpkg/core": + optional: true + "@yarnpkg/fslib": + optional: true + "@yarnpkg/plugin-npm": + optional: true + "@yarnpkg/plugin-pack": + optional: true + globby: + optional: true + semver: + optional: true + type-fest: + optional: true languageName: unknown linkType: soft @@ -8463,9 +8671,15 @@ __metadata: "@stylistic/eslint-plugin-ts": "npm:^2.0.0" "@types/jest": "npm:~29.5.2" "@types/node": "npm:^20.0.0" + "@types/semver": "npm:^7.3.13" "@typescript-eslint/eslint-plugin": "npm:^7.14.1" "@typescript-eslint/parser": "npm:^7.14.1" "@typescript-eslint/utils": "npm:^7.14.1" + "@yarnpkg/cli": "npm:^4.3.1" + "@yarnpkg/core": "npm:^4.1.1" + "@yarnpkg/fslib": "npm:^3.1.0" + "@yarnpkg/plugin-npm": "npm:^3.0.1" + "@yarnpkg/plugin-pack": "npm:^4.0.0" chokidar: "npm:^3.5.2" cpy-cli: "npm:^5.0.0" eslint: "npm:^8.57.0" @@ -8512,10 +8726,16 @@ __metadata: "@o3r/logger": "workspace:^" "@o3r/schematics": "workspace:^" "@schematics/angular": ~18.0.0 + "@yarnpkg/cli": ^4.3.1 + "@yarnpkg/core": ^4.1.1 + "@yarnpkg/fslib": ^3.1.0 + "@yarnpkg/plugin-npm": ^3.0.1 + "@yarnpkg/plugin-pack": ^4.0.0 chokidar: ^3.5.2 globby: ^11.1.0 intl-messageformat: ~10.5.0 rxjs: ^7.8.1 + semver: ^7.5.2 typescript: ~5.4.2 peerDependenciesMeta: "@angular-devkit/core": @@ -8530,10 +8750,22 @@ __metadata: optional: true "@schematics/angular": optional: true + "@yarnpkg/cli": + optional: true + "@yarnpkg/core": + optional: true + "@yarnpkg/fslib": + optional: true + "@yarnpkg/plugin-npm": + optional: true + "@yarnpkg/plugin-pack": + optional: true chokidar: optional: true globby: optional: true + semver: + optional: true typescript: optional: true languageName: unknown @@ -9381,9 +9613,15 @@ __metadata: "@stylistic/eslint-plugin-ts": "npm:^2.0.0" "@types/jest": "npm:~29.5.2" "@types/node": "npm:^20.0.0" + "@types/semver": "npm:^7.3.13" "@typescript-eslint/eslint-plugin": "npm:^7.14.1" "@typescript-eslint/parser": "npm:^7.14.1" "@typescript-eslint/utils": "npm:^7.14.1" + "@yarnpkg/cli": "npm:^4.3.1" + "@yarnpkg/core": "npm:^4.1.1" + "@yarnpkg/fslib": "npm:^3.1.0" + "@yarnpkg/plugin-npm": "npm:^3.0.1" + "@yarnpkg/plugin-pack": "npm:^4.0.0" chokidar: "npm:^3.5.2" cpy-cli: "npm:^5.0.0" eslint: "npm:^8.57.0" @@ -9426,8 +9664,14 @@ __metadata: "@o3r/logger": "workspace:^" "@o3r/schematics": "workspace:^" "@schematics/angular": ~18.0.0 + "@yarnpkg/cli": ^4.3.1 + "@yarnpkg/core": ^4.1.1 + "@yarnpkg/fslib": ^3.1.0 + "@yarnpkg/plugin-npm": ^3.0.1 + "@yarnpkg/plugin-pack": ^4.0.0 rxjs: ^7.8.1 sass: ~1.77.0 + semver: ^7.5.2 peerDependenciesMeta: "@angular-devkit/architect": optional: true @@ -9451,8 +9695,20 @@ __metadata: optional: true "@schematics/angular": optional: true + "@yarnpkg/cli": + optional: true + "@yarnpkg/core": + optional: true + "@yarnpkg/fslib": + optional: true + "@yarnpkg/plugin-npm": + optional: true + "@yarnpkg/plugin-pack": + optional: true sass: optional: true + semver: + optional: true languageName: unknown linkType: soft @@ -12721,6 +12977,13 @@ __metadata: languageName: node linkType: hard +"@types/yoga-layout@npm:1.9.2": + version: 1.9.2 + resolution: "@types/yoga-layout@npm:1.9.2" + checksum: 10/3cbcab36d9e19d077cc2bc956d3182dc26f35f13f8fcf01648717bcba412be7ed3c4b6f43c4f8f201ea815160d0cb2b96e82698c4b43d4a179c5603a7725f34e + languageName: node + linkType: hard + "@typescript-eslint/eslint-plugin@npm:^7.14.1": version: 7.15.0 resolution: "@typescript-eslint/eslint-plugin@npm:7.15.0" @@ -13218,7 +13481,50 @@ __metadata: languageName: node linkType: hard -"@yarnpkg/core@npm:^4.1.0": +"@yarnpkg/cli@npm:^4.3.1": + version: 4.3.1 + resolution: "@yarnpkg/cli@npm:4.3.1" + dependencies: + "@yarnpkg/core": "npm:^4.1.1" + "@yarnpkg/fslib": "npm:^3.1.0" + "@yarnpkg/libzip": "npm:^3.1.0" + "@yarnpkg/parsers": "npm:^3.0.2" + "@yarnpkg/plugin-compat": "npm:^4.0.6" + "@yarnpkg/plugin-constraints": "npm:^4.0.2" + "@yarnpkg/plugin-dlx": "npm:^4.0.0" + "@yarnpkg/plugin-essentials": "npm:^4.2.1" + "@yarnpkg/plugin-exec": "npm:^3.0.0" + "@yarnpkg/plugin-file": "npm:^3.0.0" + "@yarnpkg/plugin-git": "npm:^3.0.0" + "@yarnpkg/plugin-github": "npm:^3.0.0" + "@yarnpkg/plugin-http": "npm:^3.0.1" + "@yarnpkg/plugin-init": "npm:^4.0.1" + "@yarnpkg/plugin-interactive-tools": "npm:^4.0.0" + "@yarnpkg/plugin-link": "npm:^3.0.0" + "@yarnpkg/plugin-nm": "npm:^4.0.2" + "@yarnpkg/plugin-npm": "npm:^3.0.1" + "@yarnpkg/plugin-npm-cli": "npm:^4.0.4" + "@yarnpkg/plugin-pack": "npm:^4.0.0" + "@yarnpkg/plugin-patch": "npm:^4.0.1" + "@yarnpkg/plugin-pnp": "npm:^4.0.5" + "@yarnpkg/plugin-pnpm": "npm:^2.0.0" + "@yarnpkg/plugin-stage": "npm:^4.0.0" + "@yarnpkg/plugin-typescript": "npm:^4.1.1" + "@yarnpkg/plugin-version": "npm:^4.0.3" + "@yarnpkg/plugin-workspace-tools": "npm:^4.1.0" + "@yarnpkg/shell": "npm:^4.0.2" + ci-info: "npm:^3.2.0" + clipanion: "npm:^4.0.0-rc.2" + semver: "npm:^7.1.2" + tslib: "npm:^2.4.0" + typanion: "npm:^3.14.0" + peerDependencies: + "@yarnpkg/core": ^4.1.1 + checksum: 10/bf9144704c28e98f8f2b9d7d2d47cb2229b8296f6a9f4a41121b579d32c8dcedda03954aff0de0065f4b8f1a3c7db979b5bc308a8f92de5c738f28547cab5edd + languageName: node + linkType: hard + +"@yarnpkg/core@npm:^4.0.3, @yarnpkg/core@npm:^4.1.0, @yarnpkg/core@npm:^4.1.1": version: 4.1.1 resolution: "@yarnpkg/core@npm:4.1.1" dependencies: @@ -13263,6 +13569,15 @@ __metadata: languageName: node linkType: hard +"@yarnpkg/extensions@npm:^2.0.3": + version: 2.0.3 + resolution: "@yarnpkg/extensions@npm:2.0.3" + peerDependencies: + "@yarnpkg/core": ^4.0.5 + checksum: 10/cc8a1a7774680607acb00ad6d78d1fdc5a6b8446a513dbb5f2cb8a44d4c82eed78da7dc5222c91ed6a3ef2c7e298bd3f759fdcda8759d4879beb1d5c3659f4cd + languageName: node + linkType: hard + "@yarnpkg/fslib@npm:2.10.3": version: 2.10.3 resolution: "@yarnpkg/fslib@npm:2.10.3" @@ -13273,7 +13588,7 @@ __metadata: languageName: node linkType: hard -"@yarnpkg/fslib@npm:^3.0.2, @yarnpkg/fslib@npm:^3.1.0": +"@yarnpkg/fslib@npm:^3.0.0, @yarnpkg/fslib@npm:^3.0.1, @yarnpkg/fslib@npm:^3.0.2, @yarnpkg/fslib@npm:^3.1.0": version: 3.1.0 resolution: "@yarnpkg/fslib@npm:3.1.0" dependencies: @@ -13282,6 +13597,18 @@ __metadata: languageName: node linkType: hard +"@yarnpkg/libui@npm:^3.0.0": + version: 3.0.0 + resolution: "@yarnpkg/libui@npm:3.0.0" + dependencies: + tslib: "npm:^2.4.0" + peerDependencies: + ink: ^3.0.8 + react: ^16.8.4 + checksum: 10/80757636151368e538d46245352b82d47366438755c6660328c944e1727a15bd7559def013297a85d4380f43384d58dffbb4b54707878fa9609c963a4cc0fbf4 + languageName: node + linkType: hard + "@yarnpkg/libzip@npm:2.3.0, @yarnpkg/libzip@npm:^2.3.0": version: 2.3.0 resolution: "@yarnpkg/libzip@npm:2.3.0" @@ -13292,43 +13619,433 @@ __metadata: languageName: node linkType: hard -"@yarnpkg/libzip@npm:^3.1.0": - version: 3.1.0 - resolution: "@yarnpkg/libzip@npm:3.1.0" +"@yarnpkg/libzip@npm:^3.0.0, @yarnpkg/libzip@npm:^3.0.1, @yarnpkg/libzip@npm:^3.1.0": + version: 3.1.0 + resolution: "@yarnpkg/libzip@npm:3.1.0" + dependencies: + "@types/emscripten": "npm:^1.39.6" + "@yarnpkg/fslib": "npm:^3.1.0" + tslib: "npm:^2.4.0" + peerDependencies: + "@yarnpkg/fslib": ^3.1.0 + checksum: 10/d3113b362d24cea53a00afe30ca0a5589649317c25812251dd3dbc14d37779b20e00118f040dfa2fc3d9ab78f0341ed827ccc03cad859647f05eaf388a6f1890 + languageName: node + linkType: hard + +"@yarnpkg/lockfile@npm:1.1.0, @yarnpkg/lockfile@npm:^1.1.0": + version: 1.1.0 + resolution: "@yarnpkg/lockfile@npm:1.1.0" + checksum: 10/cd19e1114aaf10a05126aeea8833ef4ca8af8a46e88e12884f8359d19333fd19711036dbc2698dbe937f81f037070cf9a8da45c2e8c6ca19cafd7d15659094ed + languageName: node + linkType: hard + +"@yarnpkg/nm@npm:^4.0.2": + version: 4.0.2 + resolution: "@yarnpkg/nm@npm:4.0.2" + dependencies: + "@yarnpkg/core": "npm:^4.0.3" + "@yarnpkg/fslib": "npm:^3.0.2" + "@yarnpkg/pnp": "npm:^4.0.2" + checksum: 10/f5b1fc596bab690b45d7c6ea5479c76462736bf840bbee263e147643384876836d09b311c195366d628780de613ec5353a05226272076f3940eb3d5f23329da6 + languageName: node + linkType: hard + +"@yarnpkg/parsers@npm:3.0.0-rc.46": + version: 3.0.0-rc.46 + resolution: "@yarnpkg/parsers@npm:3.0.0-rc.46" + dependencies: + js-yaml: "npm:^3.10.0" + tslib: "npm:^2.4.0" + checksum: 10/3b7d55ebd1b90fe2adf05bfaab53031fb9ddb6315f8dfca1b5ba0393c08fc4a7f22c94603eec06dfe0a67e4121e5227b0ae57a70c73d353614650e2b54b6049d + languageName: node + linkType: hard + +"@yarnpkg/parsers@npm:^3.0.0, @yarnpkg/parsers@npm:^3.0.2": + version: 3.0.2 + resolution: "@yarnpkg/parsers@npm:3.0.2" + dependencies: + js-yaml: "npm:^3.10.0" + tslib: "npm:^2.4.0" + checksum: 10/87506f140d6c401bdd89ff22073c3dd3ec7b6858e7f576e63ec1aea1b0b8a8ec241eb46ca5582dc2071098a86d6a55c3b0628da5eeff91d33afb4fa7cac0cf65 + languageName: node + linkType: hard + +"@yarnpkg/plugin-compat@npm:^4.0.6": + version: 4.0.6 + resolution: "@yarnpkg/plugin-compat@npm:4.0.6" + dependencies: + "@yarnpkg/extensions": "npm:^2.0.3" + peerDependencies: + "@yarnpkg/core": ^4.1.1 + "@yarnpkg/plugin-patch": ^4.0.1 + checksum: 10/21d5bcbd778b6eea50671d6a3070ac2918cdc9fd5f0522e045fb4cf95e6120fd75a87f4ed8e9dee5f2238b0ab23cd456ece7ef36e29f6aa910ddd5554598c3ea + languageName: node + linkType: hard + +"@yarnpkg/plugin-constraints@npm:^4.0.2": + version: 4.0.2 + resolution: "@yarnpkg/plugin-constraints@npm:4.0.2" + dependencies: + "@yarnpkg/fslib": "npm:^3.0.1" + clipanion: "npm:^4.0.0-rc.2" + lodash: "npm:^4.17.15" + tau-prolog: "npm:^0.2.66" + tslib: "npm:^2.4.0" + peerDependencies: + "@yarnpkg/cli": ^4.0.2 + "@yarnpkg/core": ^4.0.2 + checksum: 10/ffea175adb82a7990e772039aa42f5045193b53e3a5ce6f658cbd6ea5f587f0b2cf9e7e9f881a63f0aaabfdfea41764554e5326c4f7925b600a3caf65f4156c6 + languageName: node + linkType: hard + +"@yarnpkg/plugin-dlx@npm:^4.0.0": + version: 4.0.0 + resolution: "@yarnpkg/plugin-dlx@npm:4.0.0" + dependencies: + "@yarnpkg/fslib": "npm:^3.0.0" + clipanion: "npm:^4.0.0-rc.2" + tslib: "npm:^2.4.0" + peerDependencies: + "@yarnpkg/cli": ^4.0.0 + "@yarnpkg/core": ^4.0.0 + checksum: 10/2a145c10b4714e7c4c1192705f40df5b574f58caf088decea9f24273e99aa4276c030f8d33f46512d8983de3d97d74041eafe3a4a3c9b6ad0c8489024aea190c + languageName: node + linkType: hard + +"@yarnpkg/plugin-essentials@npm:^4.2.1": + version: 4.2.1 + resolution: "@yarnpkg/plugin-essentials@npm:4.2.1" + dependencies: + "@yarnpkg/fslib": "npm:^3.1.0" + "@yarnpkg/parsers": "npm:^3.0.2" + ci-info: "npm:^3.2.0" + clipanion: "npm:^4.0.0-rc.2" + enquirer: "npm:^2.3.6" + lodash: "npm:^4.17.15" + micromatch: "npm:^4.0.2" + semver: "npm:^7.1.2" + tslib: "npm:^2.4.0" + typanion: "npm:^3.14.0" + peerDependencies: + "@yarnpkg/cli": ^4.3.1 + "@yarnpkg/core": ^4.1.1 + "@yarnpkg/plugin-git": ^3.0.0 + checksum: 10/c11d0ffa4a10411dc0d17642e1fd96caa73b374d0d7ad06d4378eda8e5d94c9a5c601de40591b57210d60a9ebb46d355f7c45a00639325dc92857bd06ba6d60d + languageName: node + linkType: hard + +"@yarnpkg/plugin-exec@npm:^3.0.0": + version: 3.0.0 + resolution: "@yarnpkg/plugin-exec@npm:3.0.0" + dependencies: + "@yarnpkg/fslib": "npm:^3.0.0" + tslib: "npm:^2.4.0" + peerDependencies: + "@yarnpkg/core": ^4.0.0 + checksum: 10/e6bbe9fc7a78f44d2853bd477edc2820bc5f707179e761bf44425b64e9caf3e802ea23379f57900cfb2ae228b083f1426bad2b201998b84982a96960502af740 + languageName: node + linkType: hard + +"@yarnpkg/plugin-file@npm:^3.0.0": + version: 3.0.0 + resolution: "@yarnpkg/plugin-file@npm:3.0.0" + dependencies: + "@yarnpkg/fslib": "npm:^3.0.0" + "@yarnpkg/libzip": "npm:^3.0.0" + tslib: "npm:^2.4.0" + peerDependencies: + "@yarnpkg/core": ^4.0.0 + checksum: 10/149e57f555666b77eaf8e4035629f28bfbb026d20dc97720b7b8ee01781639ac29db5e27212a6b4980dd67097da4baa6297786c964f1e0321137489cdcb1fd31 + languageName: node + linkType: hard + +"@yarnpkg/plugin-git@npm:^3.0.0": + version: 3.0.0 + resolution: "@yarnpkg/plugin-git@npm:3.0.0" + dependencies: + "@types/semver": "npm:^7.1.0" + "@yarnpkg/fslib": "npm:^3.0.0" + clipanion: "npm:^4.0.0-rc.2" + git-url-parse: "npm:^13.1.0" + lodash: "npm:^4.17.15" + semver: "npm:^7.1.2" + tslib: "npm:^2.4.0" + peerDependencies: + "@yarnpkg/core": ^4.0.0 + checksum: 10/b32f09b081c8c5832ac8da537c75f23a3db0afc69abf5bcf8e0a83a8c27fda05f901af5810d800014750447375c42c0724d047ab3cffc672704d2402cfdaf692 + languageName: node + linkType: hard + +"@yarnpkg/plugin-github@npm:^3.0.0": + version: 3.0.0 + resolution: "@yarnpkg/plugin-github@npm:3.0.0" + dependencies: + "@yarnpkg/fslib": "npm:^3.0.0" + tslib: "npm:^2.4.0" + peerDependencies: + "@yarnpkg/core": ^4.0.0 + "@yarnpkg/plugin-git": ^3.0.0 + checksum: 10/08f749d67f37eb18f9ac9ccc6705483ce2fbf7be57b7f7bc95408bcd3725a6b18ca2f07e3b4989e9013631dd6b278f4fc5b7e66824ee0e0d01e9475be0373aaa + languageName: node + linkType: hard + +"@yarnpkg/plugin-http@npm:^3.0.1": + version: 3.0.1 + resolution: "@yarnpkg/plugin-http@npm:3.0.1" + dependencies: + tslib: "npm:^2.4.0" + peerDependencies: + "@yarnpkg/core": ^4.0.2 + checksum: 10/928d22cf37ff90501fbbc48ddff162c63e254100fe5f01c46b979458514f14d892f577047e90ced997e4c938edbe4b65b6580f9f5c0390b6b28bce1f0b2f4804 + languageName: node + linkType: hard + +"@yarnpkg/plugin-init@npm:^4.0.1": + version: 4.0.1 + resolution: "@yarnpkg/plugin-init@npm:4.0.1" + dependencies: + "@yarnpkg/fslib": "npm:^3.0.1" + clipanion: "npm:^4.0.0-rc.2" + tslib: "npm:^2.4.0" + peerDependencies: + "@yarnpkg/cli": ^4.0.2 + "@yarnpkg/core": ^4.0.2 + checksum: 10/1a5d2bb615e9f3688568fa06530fc2eb33ad631e8bc67334c5f4534436462dfbca83a25ca0c934c00985a2ef849a3f122bdd9b754c42e3972491c74dbf0566da + languageName: node + linkType: hard + +"@yarnpkg/plugin-interactive-tools@npm:^4.0.0": + version: 4.0.0 + resolution: "@yarnpkg/plugin-interactive-tools@npm:4.0.0" + dependencies: + "@yarnpkg/libui": "npm:^3.0.0" + algoliasearch: "npm:^4.2.0" + clipanion: "npm:^4.0.0-rc.2" + diff: "npm:^5.1.0" + ink: "npm:^3.0.8" + ink-text-input: "npm:^4.0.1" + react: "npm:^16.13.1" + semver: "npm:^7.1.2" + tslib: "npm:^2.4.0" + peerDependencies: + "@yarnpkg/cli": ^4.0.0 + "@yarnpkg/core": ^4.0.0 + "@yarnpkg/plugin-essentials": ^4.0.0 + checksum: 10/640740a3e25f0e85874632dfaa31a423c48e9cfdc2f30e29f0704f5ce845f9185a964dbd438d2fcf74f9e3cb96e45f82c92ddaa499ccaf706d746c12543e0e74 + languageName: node + linkType: hard + +"@yarnpkg/plugin-link@npm:^3.0.0": + version: 3.0.0 + resolution: "@yarnpkg/plugin-link@npm:3.0.0" + dependencies: + "@yarnpkg/fslib": "npm:^3.0.0" + tslib: "npm:^2.4.0" + peerDependencies: + "@yarnpkg/core": ^4.0.0 + checksum: 10/48bdb0e7ac8f9544999237d90f8b64163c1959d9e6f6a67d14799ef1c6533d5f30c7fa896129ed7b7ac693a9ce111646878ec4f505a39f5494df5db34cc372c4 + languageName: node + linkType: hard + +"@yarnpkg/plugin-nm@npm:^4.0.2": + version: 4.0.2 + resolution: "@yarnpkg/plugin-nm@npm:4.0.2" + dependencies: + "@yarnpkg/fslib": "npm:^3.0.2" + "@yarnpkg/libzip": "npm:^3.0.1" + "@yarnpkg/nm": "npm:^4.0.2" + "@yarnpkg/parsers": "npm:^3.0.0" + "@yarnpkg/plugin-pnp": "npm:^4.0.2" + "@yarnpkg/pnp": "npm:^4.0.2" + "@zkochan/cmd-shim": "npm:^5.1.0" + clipanion: "npm:^4.0.0-rc.2" + tslib: "npm:^2.4.0" + peerDependencies: + "@yarnpkg/cli": ^4.1.0 + "@yarnpkg/core": ^4.0.3 + checksum: 10/f16811dfa3f434f51e6a1f723cc7ca1721371c88cb59b81376a9a2c4799197261ead95c3892665fcaf77a7a6c72866381efb8345bd5eaf0733319acb448dd2b9 + languageName: node + linkType: hard + +"@yarnpkg/plugin-npm-cli@npm:^4.0.4": + version: 4.0.4 + resolution: "@yarnpkg/plugin-npm-cli@npm:4.0.4" + dependencies: + "@yarnpkg/fslib": "npm:^3.0.2" + clipanion: "npm:^4.0.0-rc.2" + enquirer: "npm:^2.3.6" + micromatch: "npm:^4.0.2" + semver: "npm:^7.1.2" + tslib: "npm:^2.4.0" + typanion: "npm:^3.14.0" + peerDependencies: + "@yarnpkg/cli": ^4.2.1 + "@yarnpkg/core": ^4.0.5 + "@yarnpkg/plugin-npm": ^3.0.1 + "@yarnpkg/plugin-pack": ^4.0.0 + checksum: 10/81492ab7d041996d9f232ce027233a713d12808d0c4a38ba0678cf0732c30c6f65c752bfd2b88b2414f119cc59ab0e0ff55c0669d8e72c02eb8c73c7e0733ba0 + languageName: node + linkType: hard + +"@yarnpkg/plugin-npm@npm:^3.0.1": + version: 3.0.1 + resolution: "@yarnpkg/plugin-npm@npm:3.0.1" + dependencies: + "@yarnpkg/fslib": "npm:^3.0.2" + enquirer: "npm:^2.3.6" + lodash: "npm:^4.17.15" + semver: "npm:^7.1.2" + ssri: "npm:^6.0.1" + tslib: "npm:^2.4.0" + peerDependencies: + "@yarnpkg/core": ^4.0.3 + "@yarnpkg/plugin-pack": ^4.0.0 + checksum: 10/30c3948b90f621abbd9c60c616221683bf198643c991e222d67bf3e00f0748a16e04c978e5cf4b35a587919723836a66a9dc86fee2ee5fe18a480a00782b701c + languageName: node + linkType: hard + +"@yarnpkg/plugin-pack@npm:^4.0.0": + version: 4.0.0 + resolution: "@yarnpkg/plugin-pack@npm:4.0.0" + dependencies: + "@yarnpkg/fslib": "npm:^3.0.0" + clipanion: "npm:^4.0.0-rc.2" + micromatch: "npm:^4.0.2" + tar-stream: "npm:^2.0.1" + tslib: "npm:^2.4.0" + peerDependencies: + "@yarnpkg/cli": ^4.0.0 + "@yarnpkg/core": ^4.0.0 + checksum: 10/af36966c777a3a270257597ecbebc85297df26b2694101b7afcacad890f9ab6026762408f7ab8c27555a91a1fc550e00c38856f793041eadab491c6f15e3b876 + languageName: node + linkType: hard + +"@yarnpkg/plugin-patch@npm:^4.0.1": + version: 4.0.1 + resolution: "@yarnpkg/plugin-patch@npm:4.0.1" + dependencies: + "@yarnpkg/fslib": "npm:^3.0.1" + "@yarnpkg/libzip": "npm:^3.0.0" + clipanion: "npm:^4.0.0-rc.2" + tslib: "npm:^2.4.0" + peerDependencies: + "@yarnpkg/cli": ^4.0.2 + "@yarnpkg/core": ^4.0.2 + checksum: 10/9dd326cc4c3859ea21ad4fd7678ed8f12bd0c360c5fb7b23a2f3c45e2c6127c0cf1420b95b80ceb9271c3a7aa05b59a2eb12d8f565d47264a0d32137ab0f9464 + languageName: node + linkType: hard + +"@yarnpkg/plugin-pnp@npm:^4.0.0, @yarnpkg/plugin-pnp@npm:^4.0.2, @yarnpkg/plugin-pnp@npm:^4.0.5": + version: 4.0.5 + resolution: "@yarnpkg/plugin-pnp@npm:4.0.5" + dependencies: + "@yarnpkg/fslib": "npm:^3.1.0" + "@yarnpkg/plugin-stage": "npm:^4.0.0" + "@yarnpkg/pnp": "npm:^4.0.5" + clipanion: "npm:^4.0.0-rc.2" + micromatch: "npm:^4.0.2" + tslib: "npm:^2.4.0" + peerDependencies: + "@yarnpkg/cli": ^4.2.2 + "@yarnpkg/core": ^4.0.5 + checksum: 10/7d3277ffbb71ba8f6a1a647f4f66ff618c8645556784f3acf9fa198bbb2fad650043a8a927b7bc446f4748b1433a03219ea7030414b07417b3c31b8390631c0a + languageName: node + linkType: hard + +"@yarnpkg/plugin-pnpm@npm:^2.0.0": + version: 2.0.0 + resolution: "@yarnpkg/plugin-pnpm@npm:2.0.0" + dependencies: + "@yarnpkg/fslib": "npm:^3.0.0" + "@yarnpkg/plugin-pnp": "npm:^4.0.0" + "@yarnpkg/plugin-stage": "npm:^4.0.0" + clipanion: "npm:^4.0.0-rc.2" + p-limit: "npm:^2.2.0" + tslib: "npm:^2.4.0" + peerDependencies: + "@yarnpkg/cli": ^4.0.0 + "@yarnpkg/core": ^4.0.0 + checksum: 10/4f418b94ca77b2433d81cab39a369e710f4320359e6b16de4421b009eaedd9ddbdb181fed47fbef21d93a77dbf7f71daf31b165901d352172f8d50ef89e8e514 + languageName: node + linkType: hard + +"@yarnpkg/plugin-stage@npm:^4.0.0": + version: 4.0.0 + resolution: "@yarnpkg/plugin-stage@npm:4.0.0" dependencies: - "@types/emscripten": "npm:^1.39.6" - "@yarnpkg/fslib": "npm:^3.1.0" + "@yarnpkg/fslib": "npm:^3.0.0" + clipanion: "npm:^4.0.0-rc.2" tslib: "npm:^2.4.0" peerDependencies: - "@yarnpkg/fslib": ^3.1.0 - checksum: 10/d3113b362d24cea53a00afe30ca0a5589649317c25812251dd3dbc14d37779b20e00118f040dfa2fc3d9ab78f0341ed827ccc03cad859647f05eaf388a6f1890 + "@yarnpkg/cli": ^4.0.0 + "@yarnpkg/core": ^4.0.0 + checksum: 10/504fd0075e2cf36b168eca3be6fe5d586fb1b63d7ac2c9a3073f3dfe4341480f569602f62b3a6c233fbf99276342e1647b3b73d89f4bc8bcc920407342b00d68 languageName: node linkType: hard -"@yarnpkg/lockfile@npm:1.1.0, @yarnpkg/lockfile@npm:^1.1.0": - version: 1.1.0 - resolution: "@yarnpkg/lockfile@npm:1.1.0" - checksum: 10/cd19e1114aaf10a05126aeea8833ef4ca8af8a46e88e12884f8359d19333fd19711036dbc2698dbe937f81f037070cf9a8da45c2e8c6ca19cafd7d15659094ed +"@yarnpkg/plugin-typescript@npm:^4.1.1": + version: 4.1.1 + resolution: "@yarnpkg/plugin-typescript@npm:4.1.1" + dependencies: + "@yarnpkg/fslib": "npm:^3.0.2" + "@yarnpkg/plugin-pack": "npm:^4.0.0" + algoliasearch: "npm:^4.2.0" + semver: "npm:^7.1.2" + tslib: "npm:^2.4.0" + peerDependencies: + "@yarnpkg/cli": ^4.2.1 + "@yarnpkg/core": ^4.0.5 + "@yarnpkg/plugin-essentials": ^4.1.1 + checksum: 10/36bb32653cd442ca3ad51dcba24a46616e6fd837ff62060b92308367d84926c47309d564ec540b4c6db0e24a3fc70169153bc748df1cbec37125d00a49ae1469 languageName: node linkType: hard -"@yarnpkg/parsers@npm:3.0.0-rc.46": - version: 3.0.0-rc.46 - resolution: "@yarnpkg/parsers@npm:3.0.0-rc.46" +"@yarnpkg/plugin-version@npm:^4.0.3": + version: 4.0.3 + resolution: "@yarnpkg/plugin-version@npm:4.0.3" dependencies: - js-yaml: "npm:^3.10.0" + "@yarnpkg/fslib": "npm:^3.0.2" + "@yarnpkg/libui": "npm:^3.0.0" + "@yarnpkg/parsers": "npm:^3.0.2" + clipanion: "npm:^4.0.0-rc.2" + ink: "npm:^3.0.8" + lodash: "npm:^4.17.15" + react: "npm:^16.13.1" + semver: "npm:^7.1.2" tslib: "npm:^2.4.0" - checksum: 10/3b7d55ebd1b90fe2adf05bfaab53031fb9ddb6315f8dfca1b5ba0393c08fc4a7f22c94603eec06dfe0a67e4121e5227b0ae57a70c73d353614650e2b54b6049d + peerDependencies: + "@yarnpkg/cli": ^4.2.1 + "@yarnpkg/core": ^4.0.5 + "@yarnpkg/plugin-git": ^3.0.0 + checksum: 10/9447b9789f712d03cbafc960d64ce02fb7e32094e109ba370bf2f977af75e051ff29fd9bb135dbedc2f7cb30860417aafb15bf769b355d6a643300267dd032e1 languageName: node linkType: hard -"@yarnpkg/parsers@npm:^3.0.2": - version: 3.0.2 - resolution: "@yarnpkg/parsers@npm:3.0.2" +"@yarnpkg/plugin-workspace-tools@npm:^4.1.0": + version: 4.1.0 + resolution: "@yarnpkg/plugin-workspace-tools@npm:4.1.0" dependencies: - js-yaml: "npm:^3.10.0" + "@yarnpkg/fslib": "npm:^3.0.2" + clipanion: "npm:^4.0.0-rc.2" + micromatch: "npm:^4.0.2" + p-limit: "npm:^2.2.0" tslib: "npm:^2.4.0" - checksum: 10/87506f140d6c401bdd89ff22073c3dd3ec7b6858e7f576e63ec1aea1b0b8a8ec241eb46ca5582dc2071098a86d6a55c3b0628da5eeff91d33afb4fa7cac0cf65 + typanion: "npm:^3.14.0" + peerDependencies: + "@yarnpkg/cli": ^4.1.0 + "@yarnpkg/core": ^4.0.3 + "@yarnpkg/plugin-git": ^3.0.0 + checksum: 10/d52a27dc3a916eb11fb05fe0fa109a4301571ab19dc16537a1a335bb078d7a22ec0b6872aab0b68b79c8c70de65c4a3777164344f2df40987efc379062f92efc + languageName: node + linkType: hard + +"@yarnpkg/pnp@npm:^4.0.2, @yarnpkg/pnp@npm:^4.0.5": + version: 4.0.6 + resolution: "@yarnpkg/pnp@npm:4.0.6" + dependencies: + "@types/node": "npm:^18.17.15" + "@yarnpkg/fslib": "npm:^3.1.0" + checksum: 10/1bb9e91311a990a5592016937dba1cda20362c5497e073ca47e584709f5267e5b258ddfc967ba2db875498dbad176ab0bc57ddfd74e91794ac4efd78b6eb6b63 languageName: node linkType: hard @@ -13368,6 +14085,17 @@ __metadata: languageName: node linkType: hard +"@zkochan/cmd-shim@npm:^5.1.0": + version: 5.4.1 + resolution: "@zkochan/cmd-shim@npm:5.4.1" + dependencies: + cmd-extension: "npm:^1.0.2" + graceful-fs: "npm:^4.2.10" + is-windows: "npm:^1.0.2" + checksum: 10/b58962bbe021660b86dad817e6909b628ccc62eb67759aae952cf662486e35fcf0894caf0c700c294cb55e4a50fb81192aecae1f3d6eb24bd4495f4660b1b086 + languageName: node + linkType: hard + "@zkochan/js-yaml@npm:0.0.7": version: 0.0.7 resolution: "@zkochan/js-yaml@npm:0.0.7" @@ -13673,6 +14401,29 @@ __metadata: languageName: node linkType: hard +"algoliasearch@npm:^4.2.0": + version: 4.24.0 + resolution: "algoliasearch@npm:4.24.0" + dependencies: + "@algolia/cache-browser-local-storage": "npm:4.24.0" + "@algolia/cache-common": "npm:4.24.0" + "@algolia/cache-in-memory": "npm:4.24.0" + "@algolia/client-account": "npm:4.24.0" + "@algolia/client-analytics": "npm:4.24.0" + "@algolia/client-common": "npm:4.24.0" + "@algolia/client-personalization": "npm:4.24.0" + "@algolia/client-search": "npm:4.24.0" + "@algolia/logger-common": "npm:4.24.0" + "@algolia/logger-console": "npm:4.24.0" + "@algolia/recommend": "npm:4.24.0" + "@algolia/requester-browser-xhr": "npm:4.24.0" + "@algolia/requester-common": "npm:4.24.0" + "@algolia/requester-node-http": "npm:4.24.0" + "@algolia/transporter": "npm:4.24.0" + checksum: 10/fba851fb719529754b450c3d366de72289351c864aea56aa1c167ff0e36d5b015dddae7d720fe649a00d6c91d94a2091fb27789e553eb79c8d28a885585ccc6f + languageName: node + linkType: hard + "ansi-colors@npm:4.1.3, ansi-colors@npm:^4.1.1, ansi-colors@npm:^4.1.3": version: 4.1.3 resolution: "ansi-colors@npm:4.1.3" @@ -14088,6 +14839,13 @@ __metadata: languageName: node linkType: hard +"auto-bind@npm:4.0.0": + version: 4.0.0 + resolution: "auto-bind@npm:4.0.0" + checksum: 10/00cad71cce5742faccb7dd65c1b55ebc4f45add4b0c9a1547b10b05bab22813230133b0c892c67ba3eb969a4524710c5e43cc45c72898ec84e56f3a596e7a04f + languageName: node + linkType: hard + "autoprefixer@npm:10.4.19, autoprefixer@npm:^10.4.9": version: 10.4.19 resolution: "autoprefixer@npm:10.4.19" @@ -15131,6 +15889,13 @@ __metadata: languageName: node linkType: hard +"ci-info@npm:^2.0.0": + version: 2.0.0 + resolution: "ci-info@npm:2.0.0" + checksum: 10/3b374666a85ea3ca43fa49aa3a048d21c9b475c96eb13c133505d2324e7ae5efd6a454f41efe46a152269e9b6a00c9edbe63ec7fa1921957165aae16625acd67 + languageName: node + linkType: hard + "ci-info@npm:^3.2.0": version: 3.9.0 resolution: "ci-info@npm:3.9.0" @@ -15186,6 +15951,13 @@ __metadata: languageName: node linkType: hard +"cli-boxes@npm:^2.2.0": + version: 2.2.1 + resolution: "cli-boxes@npm:2.2.1" + checksum: 10/be79f8ec23a558b49e01311b39a1ea01243ecee30539c880cf14bf518a12e223ef40c57ead0cb44f509bffdffc5c129c746cd50d863ab879385370112af4f585 + languageName: node + linkType: hard + "cli-cursor@npm:3.1.0, cli-cursor@npm:^3.1.0": version: 3.1.0 resolution: "cli-cursor@npm:3.1.0" @@ -15231,6 +16003,16 @@ __metadata: languageName: node linkType: hard +"cli-truncate@npm:^2.1.0": + version: 2.1.0 + resolution: "cli-truncate@npm:2.1.0" + dependencies: + slice-ansi: "npm:^3.0.0" + string-width: "npm:^4.2.0" + checksum: 10/976f1887de067a8cd6ec830a7a8508336aebe6cec79b521d98ed13f67ef073b637f7305675b6247dd22f9e9cf045ec55fe746c7bdb288fbe8db0dfdc9fd52e55 + languageName: node + linkType: hard + "cli-truncate@npm:^4.0.0": version: 4.0.0 resolution: "cli-truncate@npm:4.0.0" @@ -15347,6 +16129,13 @@ __metadata: languageName: node linkType: hard +"cmd-extension@npm:^1.0.2": + version: 1.0.2 + resolution: "cmd-extension@npm:1.0.2" + checksum: 10/4cbcdd53196a3c1db3484f67aa49ed83c0e6069713f60193a94d747cb84050e8e64d688673aa5159cf0184e054cb806ceb6119e45744f721cbd3a09a3e7038cb + languageName: node + linkType: hard + "co@npm:^4.6.0": version: 4.6.0 resolution: "co@npm:4.6.0" @@ -15368,6 +16157,15 @@ __metadata: languageName: node linkType: hard +"code-excerpt@npm:^3.0.0": + version: 3.0.0 + resolution: "code-excerpt@npm:3.0.0" + dependencies: + convert-to-spaces: "npm:^1.0.1" + checksum: 10/fa3a8ed15967076a43a4093b0c824cf0ada15d9aab12ea3c028851b72a69b56495aac1eadf18c3b6ae4baf0a95bb1e1faa9dbeeb0a2b2b5ae058da23328e9dd8 + languageName: node + linkType: hard + "collect-v8-coverage@npm:^1.0.0": version: 1.0.2 resolution: "collect-v8-coverage@npm:1.0.2" @@ -15503,6 +16301,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:7.2.0, commander@npm:^7.2.0": + version: 7.2.0 + resolution: "commander@npm:7.2.0" + checksum: 10/9973af10727ad4b44f26703bf3e9fdc323528660a7590efe3aa9ad5042b4584c0deed84ba443f61c9d6f02dade54a5a5d3c95e306a1e1630f8374ae6db16c06d + languageName: node + linkType: hard + "commander@npm:8.3.0, commander@npm:^8.3.0": version: 8.3.0 resolution: "commander@npm:8.3.0" @@ -15538,13 +16343,6 @@ __metadata: languageName: node linkType: hard -"commander@npm:^7.2.0": - version: 7.2.0 - resolution: "commander@npm:7.2.0" - checksum: 10/9973af10727ad4b44f26703bf3e9fdc323528660a7590efe3aa9ad5042b4584c0deed84ba443f61c9d6f02dade54a5a5d3c95e306a1e1630f8374ae6db16c06d - languageName: node - linkType: hard - "comment-json@npm:^2.2.0": version: 2.4.2 resolution: "comment-json@npm:2.4.2" @@ -16082,6 +16880,13 @@ __metadata: languageName: node linkType: hard +"convert-to-spaces@npm:^1.0.1": + version: 1.0.2 + resolution: "convert-to-spaces@npm:1.0.2" + checksum: 10/e73f2ae39eb2b184f0796138eaab9c088b03b94937377d31be5b2282aef6a6ccce6b46f51bd99b3b7dfc70f516e2a6b16c0dd911883bfadf8d1073f462480224 + languageName: node + linkType: hard + "cookie-signature@npm:1.0.6": version: 1.0.6 resolution: "cookie-signature@npm:1.0.6" @@ -19080,6 +19885,13 @@ __metadata: languageName: node linkType: hard +"figgy-pudding@npm:^3.5.1": + version: 3.5.2 + resolution: "figgy-pudding@npm:3.5.2" + checksum: 10/1d15176fc49ce407edbecc8df286b19cf8a918900eda924609181aecec5337645e3532a01ce4154412e028ddc43f6fa558cf3916b5c9d322b6521f128da40382 + languageName: node + linkType: hard + "figures@npm:3.2.0, figures@npm:^3.0.0, figures@npm:^3.1.0": version: 3.2.0 resolution: "figures@npm:3.2.0" @@ -19851,6 +20663,25 @@ __metadata: languageName: node linkType: hard +"git-up@npm:^7.0.0": + version: 7.0.0 + resolution: "git-up@npm:7.0.0" + dependencies: + is-ssh: "npm:^1.4.0" + parse-url: "npm:^8.1.0" + checksum: 10/003ef38424702ac4cbe6d2817ccfb5811251244c955a8011ca40298d12cf1fb6529529f074d5832b5221e193ec05f4742ecf7806e6c4f41a81a2f2cff65d6bf4 + languageName: node + linkType: hard + +"git-url-parse@npm:^13.1.0": + version: 13.1.1 + resolution: "git-url-parse@npm:13.1.1" + dependencies: + git-up: "npm:^7.0.0" + checksum: 10/407f6579f3aa5e4040e215b45c1cfa7f08bd52a298a50310fc3debdd99e9d049d9f05e582b5475218116f312526691e1c3cc368e0d23f97c49735f210e381475 + languageName: node + linkType: hard + "gitconfiglocal@npm:^1.0.0": version: 1.0.0 resolution: "gitconfiglocal@npm:1.0.0" @@ -19984,6 +20815,13 @@ __metadata: languageName: node linkType: hard +"globalyzer@npm:0.1.0": + version: 0.1.0 + resolution: "globalyzer@npm:0.1.0" + checksum: 10/419a0f95ba542534fac0842964d31b3dc2936a479b2b1a8a62bad7e8b61054faa9b0a06ad9f2e12593396b9b2621cac93358d9b3071d33723fb1778608d358a1 + languageName: node + linkType: hard + "globby@npm:^11.1.0": version: 11.1.0 resolution: "globby@npm:11.1.0" @@ -20060,6 +20898,13 @@ __metadata: languageName: node linkType: hard +"globrex@npm:^0.1.2": + version: 0.1.2 + resolution: "globrex@npm:0.1.2" + checksum: 10/81ce62ee6f800d823d6b7da7687f841676d60ee8f51f934ddd862e4057316d26665c4edc0358d4340a923ac00a514f8b67c787e28fe693aae16350f4e60d55e9 + languageName: node + linkType: hard + "gopd@npm:^1.0.1": version: 1.0.1 resolution: "gopd@npm:1.0.1" @@ -20873,6 +21718,56 @@ __metadata: languageName: node linkType: hard +"ink-text-input@npm:^4.0.1": + version: 4.0.3 + resolution: "ink-text-input@npm:4.0.3" + dependencies: + chalk: "npm:^4.1.0" + type-fest: "npm:^0.15.1" + peerDependencies: + ink: ^3.0.0-3 + react: ^16.5.2 || ^17.0.0 + checksum: 10/214db7e7d0b1fb27b1a03ce21e373f71098e6fb87055782e4484fd0318c663b6d80c29a2d878622340e39dce99d66069ff3d6f9ad360cf3794511204089ea0d5 + languageName: node + linkType: hard + +"ink@npm:^3.0.8": + version: 3.2.0 + resolution: "ink@npm:3.2.0" + dependencies: + ansi-escapes: "npm:^4.2.1" + auto-bind: "npm:4.0.0" + chalk: "npm:^4.1.0" + cli-boxes: "npm:^2.2.0" + cli-cursor: "npm:^3.1.0" + cli-truncate: "npm:^2.1.0" + code-excerpt: "npm:^3.0.0" + indent-string: "npm:^4.0.0" + is-ci: "npm:^2.0.0" + lodash: "npm:^4.17.20" + patch-console: "npm:^1.0.0" + react-devtools-core: "npm:^4.19.1" + react-reconciler: "npm:^0.26.2" + scheduler: "npm:^0.20.2" + signal-exit: "npm:^3.0.2" + slice-ansi: "npm:^3.0.0" + stack-utils: "npm:^2.0.2" + string-width: "npm:^4.2.2" + type-fest: "npm:^0.12.0" + widest-line: "npm:^3.1.0" + wrap-ansi: "npm:^6.2.0" + ws: "npm:^7.5.5" + yoga-layout-prebuilt: "npm:^1.9.6" + peerDependencies: + "@types/react": ">=16.8.0" + react: ">=16.8.0" + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/cfbd8808cd1ee995440aac7a89af1156e587fec271bc3bc7460788b8b0c844eaf6364ac3d19dd4caa9f8f19bfb97d3fa0a51a5f7d89b6c6b990686ac68f083f6 + languageName: node + linkType: hard + "inquirer@npm:8.2.6, inquirer@npm:~8.2.6": version: 8.2.6 resolution: "inquirer@npm:8.2.6" @@ -21093,6 +21988,17 @@ __metadata: languageName: node linkType: hard +"is-ci@npm:^2.0.0": + version: 2.0.0 + resolution: "is-ci@npm:2.0.0" + dependencies: + ci-info: "npm:^2.0.0" + bin: + is-ci: bin.js + checksum: 10/77b869057510f3efa439bbb36e9be429d53b3f51abd4776eeea79ab3b221337fe1753d1e50058a9e2c650d38246108beffb15ccfd443929d77748d8c0cc90144 + languageName: node + linkType: hard + "is-core-module@npm:^2.13.0, is-core-module@npm:^2.5.0": version: 2.14.0 resolution: "is-core-module@npm:2.14.0" @@ -21367,6 +22273,15 @@ __metadata: languageName: node linkType: hard +"is-ssh@npm:^1.4.0": + version: 1.4.0 + resolution: "is-ssh@npm:1.4.0" + dependencies: + protocols: "npm:^2.0.1" + checksum: 10/e2d17d74a19b4368cc06ce5c76d4f625952442da337098d670a9840e1db5334c646aa0a6ed3a01e9d396901e22c755174ce64e74c3139bb10e5df03d5a6fb3fa + languageName: node + linkType: hard + "is-stream@npm:^1.1.0": version: 1.1.0 resolution: "is-stream@npm:1.1.0" @@ -21484,6 +22399,13 @@ __metadata: languageName: node linkType: hard +"is-windows@npm:^1.0.2": + version: 1.0.2 + resolution: "is-windows@npm:1.0.2" + checksum: 10/438b7e52656fe3b9b293b180defb4e448088e7023a523ec21a91a80b9ff8cdb3377ddb5b6e60f7c7de4fa8b63ab56e121b6705fe081b3cf1b828b0a380009ad7 + languageName: node + linkType: hard + "is-wsl@npm:^2.2.0": version: 2.2.0 resolution: "is-wsl@npm:2.2.0" @@ -23412,7 +24334,7 @@ __metadata: languageName: node linkType: hard -"loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0": +"loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" dependencies: @@ -24621,6 +25543,13 @@ __metadata: languageName: node linkType: hard +"node-watch@npm:0.7.3": + version: 0.7.3 + resolution: "node-watch@npm:0.7.3" + checksum: 10/40165fe737d928d06b4957f5d7924cea4c4b58d2e696986f48b6d6c26d33fda474b6f5a0cd554a31985c2184524d70c280db61c933739ff6dc5a71e990fe2dff + languageName: node + linkType: hard + "noms@npm:0.0.0": version: 0.0.0 resolution: "noms@npm:0.0.0" @@ -25022,7 +25951,7 @@ __metadata: languageName: node linkType: hard -"object-assign@npm:^4, object-assign@npm:^4.0.1, object-assign@npm:latest": +"object-assign@npm:^4, object-assign@npm:^4.0.1, object-assign@npm:^4.1.1, object-assign@npm:latest": version: 4.1.1 resolution: "object-assign@npm:4.1.1" checksum: 10/fcc6e4ea8c7fe48abfbb552578b1c53e0d194086e2e6bbbf59e0a536381a292f39943c6e9628af05b5528aa5e3318bb30d6b2e53cadaf5b8fe9e12c4b69af23f @@ -25694,6 +26623,15 @@ __metadata: languageName: node linkType: hard +"parse-path@npm:^7.0.0": + version: 7.0.0 + resolution: "parse-path@npm:7.0.0" + dependencies: + protocols: "npm:^2.0.0" + checksum: 10/2e6eadae5aff97a8b6373c1c08440bfeed814f65452674a139dc606c7c410e8e48b7983fe451aedc59802a2814121b40415ca00675c1546ff75cb73ad0c1df5a + languageName: node + linkType: hard + "parse-semver@npm:^1.1.1": version: 1.1.1 resolution: "parse-semver@npm:1.1.1" @@ -25703,6 +26641,15 @@ __metadata: languageName: node linkType: hard +"parse-url@npm:^8.1.0": + version: 8.1.0 + resolution: "parse-url@npm:8.1.0" + dependencies: + parse-path: "npm:^7.0.0" + checksum: 10/ceb51dc474568092a50d6d936036dfe438a87aa45bcf20947c8fcdf1544ee9c50255608abae604644e718e91e0b83cfbea4675e8b2fd90bc197432f6d9be263c + languageName: node + linkType: hard + "parse5-html-rewriting-stream@npm:7.0.0": version: 7.0.0 resolution: "parse5-html-rewriting-stream@npm:7.0.0" @@ -25766,6 +26713,13 @@ __metadata: languageName: node linkType: hard +"patch-console@npm:^1.0.0": + version: 1.0.0 + resolution: "patch-console@npm:1.0.0" + checksum: 10/8cd738aa470f2e9463fca35da6a19403384ac555004f698ddd3dfdb69135ab60fe9bd2edd1dbdd8c09d92c0a2190fd0f7337fe48123013baf8ffec8532885a3a + languageName: node + linkType: hard + "path-browserify@npm:^1.0.1": version: 1.0.1 resolution: "path-browserify@npm:1.0.1" @@ -26885,6 +27839,17 @@ __metadata: languageName: node linkType: hard +"prop-types@npm:^15.6.2": + version: 15.8.1 + resolution: "prop-types@npm:15.8.1" + dependencies: + loose-envify: "npm:^1.4.0" + object-assign: "npm:^4.1.1" + react-is: "npm:^16.13.1" + checksum: 10/7d959caec002bc964c86cdc461ec93108b27337dabe6192fb97d69e16a0c799a03462713868b40749bfc1caf5f57ef80ac3e4ffad3effa636ee667582a75e2c0 + languageName: node + linkType: hard + "propagate@npm:^2.0.0": version: 2.0.1 resolution: "propagate@npm:2.0.1" @@ -26901,6 +27866,13 @@ __metadata: languageName: node linkType: hard +"protocols@npm:^2.0.0, protocols@npm:^2.0.1": + version: 2.0.1 + resolution: "protocols@npm:2.0.1" + checksum: 10/0cd08a55b9cb7cc96fed7a528255320428a7c86fd5f3f35965845285436433b7836178893168f80584efdf86391cd7c0a837b6f6bc5ddac3029c76be61118ba5 + languageName: node + linkType: hard + "protractor@npm:^7.0.0": version: 7.0.0 resolution: "protractor@npm:7.0.0" @@ -27129,6 +28101,19 @@ __metadata: languageName: node linkType: hard +"qunit@npm:^2.8.0": + version: 2.21.0 + resolution: "qunit@npm:2.21.0" + dependencies: + commander: "npm:7.2.0" + node-watch: "npm:0.7.3" + tiny-glob: "npm:0.2.9" + bin: + qunit: bin/qunit.js + checksum: 10/5ce549d79544354bed62db2849f4e650dd41119e851d7172add3eec5ca70fc17c57a2e7c4e0b3884d40c792be1c090b2ecc732eff7429be80bde8765dc125891 + languageName: node + linkType: hard + "ramda@npm:0.29.0": version: 0.29.0 resolution: "ramda@npm:0.29.0" @@ -27178,6 +28163,16 @@ __metadata: languageName: node linkType: hard +"react-devtools-core@npm:^4.19.1": + version: 4.28.5 + resolution: "react-devtools-core@npm:4.28.5" + dependencies: + shell-quote: "npm:^1.6.1" + ws: "npm:^7" + checksum: 10/7c951a6a9b773e4fd56b2f1894c83aaec417373cf01aa261bd2dd286e6c6f1d8c67a3749ecb1d106dbf9e8cda0e6ed1bfd6ce1b61c81e035f2527be3dd9eebc2 + languageName: node + linkType: hard + "react-dom@npm:^18.0.0": version: 18.3.1 resolution: "react-dom@npm:18.3.1" @@ -27190,6 +28185,13 @@ __metadata: languageName: node linkType: hard +"react-is@npm:^16.13.1": + version: 16.13.1 + resolution: "react-is@npm:16.13.1" + checksum: 10/5aa564a1cde7d391ac980bedee21202fc90bdea3b399952117f54fb71a932af1e5902020144fb354b4690b2414a0c7aafe798eb617b76a3d441d956db7726fdf + languageName: node + linkType: hard + "react-is@npm:^18.0.0": version: 18.3.1 resolution: "react-is@npm:18.3.1" @@ -27197,6 +28199,19 @@ __metadata: languageName: node linkType: hard +"react-reconciler@npm:^0.26.2": + version: 0.26.2 + resolution: "react-reconciler@npm:0.26.2" + dependencies: + loose-envify: "npm:^1.1.0" + object-assign: "npm:^4.1.1" + scheduler: "npm:^0.20.2" + peerDependencies: + react: ^17.0.2 + checksum: 10/7b9369a12e57859088aaef052abe03138ad8eefe67308bf8be6ef8f529be06276dc4977a4d665dc9b9e08188bd308b2a0d58dc181253c0205c98e03d7c0901b7 + languageName: node + linkType: hard + "react-remove-scroll-bar@npm:^2.3.4": version: 2.3.6 resolution: "react-remove-scroll-bar@npm:2.3.6" @@ -27249,6 +28264,17 @@ __metadata: languageName: node linkType: hard +"react@npm:^16.13.1": + version: 16.14.0 + resolution: "react@npm:16.14.0" + dependencies: + loose-envify: "npm:^1.1.0" + object-assign: "npm:^4.1.1" + prop-types: "npm:^15.6.2" + checksum: 10/ee04c82f5ecb70fe15a48d8cfc3fb20ce2f7e65277d4adcb56a0ac2b82c54550d4c65eabce0d5dc0cc90d053831b9586d72ee515b11cdf0c5436c7f95aafdcda + languageName: node + linkType: hard + "react@npm:^18.0.0": version: 18.3.1 resolution: "react@npm:18.3.1" @@ -27431,6 +28457,13 @@ __metadata: languageName: node linkType: hard +"readline-sync@npm:1.4.9": + version: 1.4.9 + resolution: "readline-sync@npm:1.4.9" + checksum: 10/4eb3e21ec9f48256cdb21a72166d8acfcdf4c5904c913b0715978280c6cd226e9c51df39aa9d119aaaf926063755aa440e7d0bab4994de5bb5db38e3ac08cabf + languageName: node + linkType: hard + "real-require@npm:^0.2.0": version: 0.2.0 resolution: "real-require@npm:0.2.0" @@ -28207,6 +29240,16 @@ __metadata: languageName: node linkType: hard +"scheduler@npm:^0.20.2": + version: 0.20.2 + resolution: "scheduler@npm:0.20.2" + dependencies: + loose-envify: "npm:^1.1.0" + object-assign: "npm:^4.1.1" + checksum: 10/898917fa475386953d998add9107c04bf2c335eee86172833995dee126d12a68bee3c29edbd61fa0bcbcb8ee511c422eaab23b86b02f95aab26ecfaed8df5e64 + languageName: node + linkType: hard + "scheduler@npm:^0.23.2": version: 0.23.2 resolution: "scheduler@npm:0.23.2" @@ -28470,7 +29513,7 @@ __metadata: languageName: node linkType: hard -"shell-quote@npm:^1.7.3, shell-quote@npm:^1.8.1": +"shell-quote@npm:^1.6.1, shell-quote@npm:^1.7.3, shell-quote@npm:^1.8.1": version: 1.8.1 resolution: "shell-quote@npm:1.8.1" checksum: 10/af19ab5a1ec30cb4b2f91fd6df49a7442d5c4825a2e269b3712eded10eedd7f9efeaab96d57829880733fc55bcdd8e9b1d8589b4befb06667c731d08145e274d @@ -28589,6 +29632,17 @@ __metadata: languageName: node linkType: hard +"slice-ansi@npm:^3.0.0": + version: 3.0.0 + resolution: "slice-ansi@npm:3.0.0" + dependencies: + ansi-styles: "npm:^4.0.0" + astral-regex: "npm:^2.0.0" + is-fullwidth-code-point: "npm:^3.0.0" + checksum: 10/5ec6d022d12e016347e9e3e98a7eb2a592213a43a65f1b61b74d2c78288da0aded781f665807a9f3876b9daa9ad94f64f77d7633a0458876c3a4fdc4eb223f24 + languageName: node + linkType: hard + "slice-ansi@npm:^4.0.0": version: 4.0.0 resolution: "slice-ansi@npm:4.0.0" @@ -28952,6 +30006,15 @@ __metadata: languageName: node linkType: hard +"ssri@npm:^6.0.1": + version: 6.0.2 + resolution: "ssri@npm:6.0.2" + dependencies: + figgy-pudding: "npm:^3.5.1" + checksum: 10/7f8062604b50bd647ee11c6e03bc0d8f39d9dfe3bd871f711676c1ab862435feb1dae40b20ca44fa27ef1485b814bb769d4557ff6af7e5c28bb18db3aba64510 + languageName: node + linkType: hard + "stack-trace@npm:0.0.x": version: 0.0.10 resolution: "stack-trace@npm:0.0.10" @@ -28959,7 +30022,7 @@ __metadata: languageName: node linkType: hard -"stack-utils@npm:^2.0.3": +"stack-utils@npm:^2.0.2, stack-utils@npm:^2.0.3": version: 2.0.6 resolution: "stack-utils@npm:2.0.6" dependencies: @@ -29068,7 +30131,7 @@ __metadata: languageName: node linkType: hard -"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.0.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.2, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" dependencies: @@ -29575,7 +30638,7 @@ __metadata: languageName: node linkType: hard -"tar-stream@npm:^2.1.4, tar-stream@npm:~2.2.0": +"tar-stream@npm:^2.0.1, tar-stream@npm:^2.1.4, tar-stream@npm:~2.2.0": version: 2.2.0 resolution: "tar-stream@npm:2.2.0" dependencies: @@ -29627,6 +30690,16 @@ __metadata: languageName: node linkType: hard +"tau-prolog@npm:^0.2.66": + version: 0.2.81 + resolution: "tau-prolog@npm:0.2.81" + dependencies: + qunit: "npm:^2.8.0" + readline-sync: "npm:1.4.9" + checksum: 10/b28f9064e454deb2fcb5b58953e14675a57508056ea62a21477775de4b742cd50841adae5ad2ddf19c37747f5e0c55b39c7befdab27aa8e13d3b933f2216bfac + languageName: node + linkType: hard + "teex@npm:^1.0.1": version: 1.0.1 resolution: "teex@npm:1.0.1" @@ -29843,6 +30916,16 @@ __metadata: languageName: node linkType: hard +"tiny-glob@npm:0.2.9": + version: 0.2.9 + resolution: "tiny-glob@npm:0.2.9" + dependencies: + globalyzer: "npm:0.1.0" + globrex: "npm:^0.1.2" + checksum: 10/5fb773747f6a8fcae4b8884642901fa7b884879695186c422eb24b2213dfe90645f34225ced586329b3080d850472ea938646ab1c8b3a2989f9fa038fef8eee3 + languageName: node + linkType: hard + "tiny-inflate@npm:^1.0.0, tiny-inflate@npm:^1.0.2": version: 1.0.3 resolution: "tiny-inflate@npm:1.0.3" @@ -30308,7 +31391,7 @@ __metadata: languageName: node linkType: hard -"typanion@npm:^3.8.0": +"typanion@npm:^3.14.0, typanion@npm:^3.8.0": version: 3.14.0 resolution: "typanion@npm:3.14.0" checksum: 10/5e88d9e6121ff0ec543f572152fdd1b70e9cca35406d79013ec8e08defa8ef96de5fec9e98da3afbd1eb4426b9e8e8fe423163d0b482e34a40103cab1ef29abd @@ -30331,6 +31414,20 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^0.12.0": + version: 0.12.0 + resolution: "type-fest@npm:0.12.0" + checksum: 10/828dd234a0497721622de2907147aff3290a42f86ca01b3d1c1273b4f50bcd00eadcb71c7fad9b34125c7796b8d3a554415f9dda4875993ed51636431488f712 + languageName: node + linkType: hard + +"type-fest@npm:^0.15.1": + version: 0.15.1 + resolution: "type-fest@npm:0.15.1" + checksum: 10/0468c369e3cb6054c59db7eb5846ee9a81d46185d0ddbbb3f6a6122e88508dee4e3a3fd3d74b062d7be6b6ed1f49084f94b605cea395f2fa16dfc4649aec20a6 + languageName: node + linkType: hard + "type-fest@npm:^0.18.0": version: 0.18.1 resolution: "type-fest@npm:0.18.1" @@ -31709,6 +32806,15 @@ __metadata: languageName: node linkType: hard +"widest-line@npm:^3.1.0": + version: 3.1.0 + resolution: "widest-line@npm:3.1.0" + dependencies: + string-width: "npm:^4.0.0" + checksum: 10/03db6c9d0af9329c37d74378ff1d91972b12553c7d72a6f4e8525fe61563fa7adb0b9d6e8d546b7e059688712ea874edd5ded475999abdeedf708de9849310e0 + languageName: node + linkType: hard + "wildcard@npm:^2.0.0": version: 2.0.1 resolution: "wildcard@npm:2.0.1" @@ -31867,7 +32973,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:^7.0.0": +"ws@npm:^7, ws@npm:^7.0.0, ws@npm:^7.5.5": version: 7.5.10 resolution: "ws@npm:7.5.10" peerDependencies: @@ -32157,6 +33263,15 @@ __metadata: languageName: node linkType: hard +"yoga-layout-prebuilt@npm:^1.9.6": + version: 1.10.0 + resolution: "yoga-layout-prebuilt@npm:1.10.0" + dependencies: + "@types/yoga-layout": "npm:1.9.2" + checksum: 10/fe36fadae9b30710083f76c73e87479c2eb291ff7c560c35a9e2b8eb78f43882ace63cc80cdaecae98ee2e4168e1bf84dc65b2f5ae1bfa31df37603c46683bd6 + languageName: node + linkType: hard + "zepto@npm:^1.2.0": version: 1.2.0 resolution: "zepto@npm:1.2.0"