Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 65 additions & 185 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@fortawesome/free-brands-svg-icons": "^7.1.0",
"@fortawesome/free-regular-svg-icons": "^7.1.0",
"@fortawesome/free-solid-svg-icons": "^7.1.0",
"@inquirer/prompts": "^7.10.1",
"@ngrx/component": "^21",
"@ngrx/effects": "^21",
"@ngrx/entity": "^21",
Expand Down Expand Up @@ -69,6 +70,7 @@
"@ngrx/schematics": "^21",
"@ngrx/store-devtools": "^21",
"@nx/azure-cache": "^4.0.0",
"@nx/devkit": "^20.8.4",
"@schematics/angular": "^21",
"@stylistic/eslint-plugin": "^2.7.2",
"@stylistic/stylelint-plugin": "^5.0.0",
Expand Down
14 changes: 14 additions & 0 deletions tools/schematics/builders.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"builders": {
"application": {
"implementation": "./versioning/application-builder",
"schema": "./versioning/daff.schema.json",
"description": "Wraps the angular application builder with driver conditions."
},
"sync": {
"implementation": "./versioning/sync-builder",
"schema": "./versioning/daff.schema.json",
"description": "Modifies application build conditions for the specified driver versions."
}
}
}
2 changes: 2 additions & 0 deletions tools/schematics/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"author": "Daffodil Team",
"license": "MIT",
"schematics": "./collection.json",
"builders": "./builders.json",
"ng-add": {
"save": "dependencies"
},
Expand Down Expand Up @@ -47,6 +48,7 @@
"@daffodil/product": "0.0.0-PLACEHOLDER"
},
"peerDependencies": {
"@angular/build": "0.0.0-PLACEHOLDER",
"@angular/core": "0.0.0-PLACEHOLDER",
"@angular/common": "0.0.0-PLACEHOLDER",
"@angular/router": "0.0.0-PLACEHOLDER"
Expand Down
2 changes: 2 additions & 0 deletions tools/schematics/scripts/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ DIST="../../dist/commerce"
tsc -p tsconfig.json

cp collection.json "$DIST/"
cp builders.json "$DIST/"
cp ng-add/schema.json "$DIST/ng-add/"
cp versioning/daff.schema.json "$DIST/versioning/"
cp -r ng-add/files "$DIST/ng-add/"
cp README.md "$DIST/"

Expand Down
2 changes: 2 additions & 0 deletions tools/schematics/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"rootDir": ".",
"module": "node20",
"moduleResolution": "node16",
"allowImportingTsExtensions": true,
"rewriteRelativeImportExtensions": true,
"target": "es2023",
"lib": [
"es2023"
Expand Down
3 changes: 2 additions & 1 deletion tools/schematics/tsconfig.spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
],
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true
"skipLibCheck": true,
"resolveJsonModule": true
},
"include": [
"**/*.spec.ts",
Expand Down
46 changes: 46 additions & 0 deletions tools/schematics/versioning/application-builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { ApplicationBuilderOptions } from '@angular/build';
import {
Builder,
BuilderContext,
BuilderOutput,
createBuilder,
} from '@angular-devkit/architect';
import {
from,
Observable,
} from 'rxjs';
import { switchMap } from 'rxjs/operators';

import { collectPlatformVersions } from './collect-platform-versions';
import { daffVersioningGetConditions } from './get-conditions';
import { DaffVersioningProject } from './project.type';

interface Options extends DaffVersioningProject, ApplicationBuilderOptions {}

/**
* A builder which wraps `@angular/build:application` and enables the automatic managing of the
* build conditions needed to support the driver auto versioning feature.
*
* See {@link DaffVersioningProject} for configuration options.
*/
const builder: Builder<any> = createBuilder((options: Options, context: BuilderContext): Observable<BuilderOutput> => {
const b = collectPlatformVersions([`${context.workspaceRoot}/node_modules/@daffodil/**`, ...options.packages.map((p) => `${context.workspaceRoot}/${p}`)]).then((packages) =>
daffVersioningGetConditions(options, packages),
).then((conditions) => {
context.logger.info(`Running application builder with the following conditions: ${conditions}`);
delete (<any>options).drivers;
delete (<any>options).packages;
return context.scheduleBuilder('@angular/build:application', {
...options,
conditions: conditions.concat(options.conditions ?? []),
}, {
target: context.target,
});
});

return from(b).pipe(
switchMap((run) => run.output),
);
});

export default builder;
71 changes: 71 additions & 0 deletions tools/schematics/versioning/collect-platform-versions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import {
glob,
readFile,
} from 'node:fs/promises';

import { DaffPackagePlatformVersions } from './packages.type';
import { daffVersioningIsSupportedPlatform } from './validate-platform';
import { DaffVersionString } from './version.type';

// from https://github.com/sindresorhus/type-fest/blob/main/source/package-json.d.ts
/**
* A mapping of conditions and the paths to which they resolve.
*/
interface ExportConditions {
[condition: string]: Exports;
}

/**
* Entry points of a module, optionally with conditions and subpath exports.
*/
type Exports =
| null
| string
| Array<string | ExportConditions>
| ExportConditions;

interface PackageJson {
name: string;
exports: Exports;
}
//

const unique = <T,>(array: T[], comparator: (a: T, b: T) => boolean = (a, b) => a === b): T[] =>
array.filter((a, index) => array.slice(index + 1).filter(b => comparator(a, b)).length === 0);

/**
* Searches for packages containing `exports` that declare drivers using auto versioning.
*
* @param searchGlobs Globs under which to search for `package.json`s.
*/
export async function collectPlatformVersions(searchGlobs: Array<string>): Promise<DaffPackagePlatformVersions> {
const packages = await searchGlobs.reduce(async (acc, searchGlob) => {
const ret: Array<PackageJson> = [];
for await (const entry of glob(`${searchGlob}/package.json`)) {
const packageJson: PackageJson = JSON.parse(await readFile(entry, 'utf-8'));
ret.push(packageJson);
}

return acc.then((v) => v.concat(ret));
}, <Promise<Array<PackageJson>>>Promise.resolve([]));

return packages.reduce((acc, packageJson) => {
const packageName = packageJson.name?.match(/@daffodil\/(?<package>.*)/)?.groups?.package;
if (packageName && packageJson.exports) {
Object.entries(packageJson.exports).forEach(([platformExport, overrides]) => {
const platform = platformExport.match(/\.\/driver\/(?<platform>\w*)\/auto/)?.groups?.platform;
if (daffVersioningIsSupportedPlatform(platform) && overrides) {
Object.keys(overrides).forEach((condition) => {
const version = condition.match(new RegExp(`${packageName}-${platform}-(?<version>[\\w-.]*)`))?.groups?.version;
if (version) {
acc[platform] ??= {};
acc[platform][packageName] = unique([...acc[platform][packageName] ?? [], <DaffVersionString>version]);
}
});
}
});
}

return acc;
}, <DaffPackagePlatformVersions>{});
}
28 changes: 28 additions & 0 deletions tools/schematics/versioning/daff.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"type": "object",
"properties": {
"drivers": {
"type": "object",
"properties": {
"magento": {
"type": "string"
}
},
"additionalProperties": false,
"description": "The version of drivers that the application targets."
},
"packages": {
"type": "array",
"items": {
"type": "string"
},
"additionalProperties": false,
"description": "Globs, relative to the workspace root, pointing to node modules that contain auto versioned daffodil driver packages. First party daffodil drivers are automatically included; only include third party drivers that need to be auto versioned."
},
"$schema": {
"type": "string"
}
},
"additionalProperties": true
}
85 changes: 85 additions & 0 deletions tools/schematics/versioning/get-conditions.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { DaffVersioningProject } from './project.type';
import { daffVersioningGetConditions } from './get-conditions';
import { DaffPackagePlatformVersions } from './packages.type';

describe('@daffodil/commerce/versioning | daffVersioningGetConditions', () => {
it('returns a condition for each package supporting the driver version', () => {
const daff: DaffVersioningProject = { drivers: { magento: '2.4.5' }};
const packages: DaffPackagePlatformVersions = {
magento: {
'some-package': [<const>'2.4.5'],
'other-package': [<const>'2.4.5'],
},
};

expect(daffVersioningGetConditions(daff, packages)).toEqual([
'some-package-magento-2.4.5',
'other-package-magento-2.4.5',
]);
});

it('ignores drivers with an undefined version', () => {
const daff: DaffVersioningProject = { drivers: { magento: undefined }};
const packages: DaffPackagePlatformVersions = {
magento: { 'some-package': [<const>'2.4.5']},
};

expect(daffVersioningGetConditions(daff, packages)).toEqual([]);
});

it('throws when a driver version is empty', () => {
const daff: DaffVersioningProject = { drivers: { magento: <any>'' }};

expect(() => daffVersioningGetConditions(daff, {})).toThrowError(
/Platform magento is missing a driver version; cannot sync\./,
);
});

it('throws when a driver platform is unsupported', () => {
const daff = <DaffVersioningProject><unknown>{ drivers: { bogus: '1.0.0' }};

expect(() => daffVersioningGetConditions(daff, {})).toThrowError(
/bogus is unsupported\. Supported platforms are magento, shopify/,
);
});

it('returns no conditions when packages has no entry for the platform', () => {
const daff: DaffVersioningProject = { drivers: { magento: '2.4.5' }};

expect(daffVersioningGetConditions(daff, {})).toEqual([]);
});

it('skips packages that have no supported version and warns', () => {
spyOn(console, 'warn');
const daff: DaffVersioningProject = { drivers: { magento: '2.4.5' }};
const packages: DaffPackagePlatformVersions = {
magento: { 'some-package': [<const>'2.4.6']},
};

expect(daffVersioningGetConditions(daff, packages)).toEqual([]);
expect(console.warn).toHaveBeenCalledWith(
jasmine.stringMatching('No supported magento version found for @daffodil/some-package'),
);
});

it('does not build conditions for non-magento platforms', () => {
const daff: DaffVersioningProject = { drivers: { shopify: '1.0.0' }};
const packages: DaffPackagePlatformVersions = {
shopify: { 'some-package': [<const>'1.0.0']},
};

expect(daffVersioningGetConditions(daff, packages)).toEqual([]);
});

it('accumulates conditions across multiple platforms', () => {
const daff: DaffVersioningProject = { drivers: { magento: '2.4.5', shopify: '1.0.0' }};
const packages: DaffPackagePlatformVersions = {
magento: { 'some-package': [<const>'2.4.5']},
shopify: { 'other-package': [<const>'1.0.0']},
};

expect(daffVersioningGetConditions(daff, packages)).toEqual([
'some-package-magento-2.4.5',
]);
});
});
40 changes: 40 additions & 0 deletions tools/schematics/versioning/get-conditions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { DaffPackagePlatformVersions } from './packages.type';
import { DAFF_VERSIONING_PLATFORMS } from './platforms.const';
import { DaffVersioningProject } from './project.type';
import { magentoFindSupportedVersion } from './public_api';
import { daffVersioningIsSupportedPlatform } from './validate-platform';

/**
* Based on the available drivers, compiles a list of build conditions that will satisfy the requested driver versions.
*
* @param daff The requested driver versions.
* @param packages The available drivers.
*/
export const daffVersioningGetConditions = (daff: DaffVersioningProject, packages: DaffPackagePlatformVersions) =>
Object.entries(daff.drivers).reduce((conditions, [platform, version]) => {
if (version === undefined) {
return conditions;
}
if (!version) {
throw new Error(
`Platform ${platform} is missing a driver version; cannot sync.`,
);
}
if (!daffVersioningIsSupportedPlatform(platform)) {
throw new Error(`${platform} is unsupported. Supported platforms are ${DAFF_VERSIONING_PLATFORMS.join(', ')}`);
}

return packages[platform]
? Object.entries(packages[platform]).reduce((acc, [packageName, versions]) => {
if (platform === 'magento') {
const supportedVersion = magentoFindSupportedVersion(versions, version);
if (supportedVersion) {
acc.push(`${packageName}-${platform}-${supportedVersion}`);
} else {
console.warn(`No supported ${platform} version found for @daffodil/${packageName}. Supported versions are ${versions}`);
}
}
return acc;
}, conditions)
: conditions;
}, <Array<string>>[]);
Loading
Loading