Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: schematics for guide (2) - modernize-apps-migrated-from-2211.32-to-2211.35.md #19969

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
98c7f80
initial version
Platonn Feb 5, 2025
979f940
make the functions for printing fallback docs to contain the version …
Platonn Feb 5, 2025
9e95d79
use aliased import of util function, for readability
Platonn Feb 5, 2025
a4f3a87
add JSDocs and improve error handling
Platonn Feb 5, 2025
50e281f
fix description of schematic in collection.json
Platonn Feb 5, 2025
cde1380
Merge branch 'kris-schematics--guide1-develop' into kris-schematics--…
Platonn Feb 5, 2025
f5f09b6
wording of FALLBACK_ADVICE_TO_FOLLOW_DOCS + dont export it
Platonn Feb 5, 2025
944d270
improve logic, fix error with tree.create(directory), improve logs
Platonn Feb 5, 2025
f855a21
Add license header
github-actions[bot] Feb 5, 2025
81c2ea3
Merge branch 'kris-schematics--guide1-develop' into kris-schematics--…
Platonn Feb 6, 2025
c587d7e
Merge branch 'kris-schematics--guide2-develop' of github.com:SAP/spar…
Platonn Feb 6, 2025
36e42cb
fix URL for docs
Platonn Feb 6, 2025
2674807
use existing util checkIfSSRIsUsedWithApplicationBuilder
Platonn Feb 6, 2025
99fce8b
use enum for old/new path of server.ts
Platonn Feb 6, 2025
aa648b9
fix moveAssetsToPublic(). previously it only deleted files instead of…
Platonn Feb 6, 2025
617bef7
make logs about moved files more concise
Platonn Feb 6, 2025
f03045d
fix logs for moving favico
Platonn Feb 6, 2025
33460ea
use enum for moveAssetsToPublic
Platonn Feb 6, 2025
6c9da17
fix(update i18n lazy loading config): allow for both syntax:
Platonn Feb 6, 2025
21f85d6
refactor: rename checkIfSSRIsUsedWithApplicationBuilder to isSsrUsed
Platonn Feb 6, 2025
520c813
Merge branch 'kris-schematics--guide1-develop' into kris-schematics--…
Platonn Feb 6, 2025
0da6558
Merge branch 'kris-schematics--guide1-develop' into kris-schematics--…
Platonn Feb 6, 2025
e901d06
Merge branch 'kris-schematics--guide1-develop' into kris-schematics--…
Platonn Feb 6, 2025
906966b
empty commit
Platonn Feb 6, 2025
0879994
Merge branch 'kris-schematics--guide1-develop' into kris-schematics--…
Platonn Feb 6, 2025
4e29541
don't use ApplicationBuilderOptions
Platonn Feb 6, 2025
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
4 changes: 4 additions & 0 deletions projects/schematics/src/collection.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
"modernize-app-migrated-from-6_8-to-2211_19": {
"description": "Modernize Angular application migrated from 6.8 to 22.11.19",
"factory": "./modernize-app-migrated-from-6_8-to-2211_19/index#migrate"
},
"modernize-app-migrated-from-2211_32-to-2211_35": {
"description": "Modernize Angular application migrated from 2211.32 to 2211.35",
"factory": "./modernize-app-migrated-from-2211_32-to-2211_35/index#migrate"
}
}
}
6 changes: 2 additions & 4 deletions projects/schematics/src/migrations/2211_35/ssr/ssr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@
*/

import { noop, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
import { checkIfSSRIsUsedWithApplicationBuilder } from '../../../shared/utils/package-utils';
import { isSsrUsed } from '../../../shared/utils/package-utils';
import { updateServerFile } from './update-ssr/update-server-files';

export function migrate(): Rule {
return (tree: Tree, _context: SchematicContext) => {
return checkIfSSRIsUsedWithApplicationBuilder(tree)
? updateServerFile()
: noop();
return isSsrUsed(tree) ? updateServerFile() : noop();
};
}
34 changes: 9 additions & 25 deletions projects/schematics/src/migrations/2211_35/ssr/ssr_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as shared from '../../../shared/utils/package-utils';

jest.mock('../../../shared/utils/package-utils', () => ({
...jest.requireActual('../../../shared/utils/package-utils'),
checkIfSSRIsUsedWithApplicationBuilder: jest.fn(),
isSsrUsed: jest.fn(),
}));

const collectionPath = join(__dirname, '../../migrations.json');
Expand Down Expand Up @@ -68,9 +68,7 @@ describe('Update SSR Migration', () => {
it.each(['/server.ts', '/src/server.ts'])(
'should update %s when using application builder and SSR is used',
async (filePath) => {
(
shared.checkIfSSRIsUsedWithApplicationBuilder as jest.Mock
).mockReturnValue(true);
(shared.isSsrUsed as jest.Mock).mockReturnValue(true);
tree.create(filePath, serverFileContent);

const newTree = await runner.runSchematic(
Expand All @@ -83,16 +81,12 @@ describe('Update SSR Migration', () => {
expect(content).toContain('export function app()');
expect(content).toContain("join(serverDistFolder, 'index.server.html')");
expect(content).not.toContain("join(browserDistFolder, 'index.html')");
expect(
shared.checkIfSSRIsUsedWithApplicationBuilder
).toHaveBeenCalledWith(tree);
expect(shared.isSsrUsed).toHaveBeenCalledWith(tree);
}
);

it('should not update when SSR is not used', async () => {
(
shared.checkIfSSRIsUsedWithApplicationBuilder as jest.Mock
).mockReturnValue(false);
(shared.isSsrUsed as jest.Mock).mockReturnValue(false);
tree.create('/server.ts', serverFileContent);

const newTree = await runner.runSchematic(MIGRATION_SCRIPT_NAME, {}, tree);
Expand All @@ -102,29 +96,21 @@ describe('Update SSR Migration', () => {
expect(content).not.toContain(
"join(serverDistFolder, 'index.server.html')"
);
expect(shared.checkIfSSRIsUsedWithApplicationBuilder).toHaveBeenCalledWith(
tree
);
expect(shared.isSsrUsed).toHaveBeenCalledWith(tree);
});

it('should handle missing server.ts file', async () => {
(
shared.checkIfSSRIsUsedWithApplicationBuilder as jest.Mock
).mockReturnValue(true);
(shared.isSsrUsed as jest.Mock).mockReturnValue(true);

const newTree = await runner.runSchematic(MIGRATION_SCRIPT_NAME, {}, tree);

expect(newTree.exists('/server.ts')).toBe(false);
expect(newTree.exists('/src/server.ts')).toBe(false);
expect(shared.checkIfSSRIsUsedWithApplicationBuilder).toHaveBeenCalledWith(
tree
);
expect(shared.isSsrUsed).toHaveBeenCalledWith(tree);
});

it('should preserve other join statements when SSR is used', async () => {
(
shared.checkIfSSRIsUsedWithApplicationBuilder as jest.Mock
).mockReturnValue(true);
(shared.isSsrUsed as jest.Mock).mockReturnValue(true);

const contentWithMultipleJoins = `
const otherFile = join(process.cwd(), 'other.html');
Expand All @@ -140,8 +126,6 @@ describe('Update SSR Migration', () => {
expect(content).toContain("join(process.cwd(), 'other.html')");
expect(content).toContain('join(serverDistFolder, "index.server.html")');
expect(content).toContain("join(process.cwd(), 'another.html')");
expect(shared.checkIfSSRIsUsedWithApplicationBuilder).toHaveBeenCalledWith(
tree
);
expect(shared.isSsrUsed).toHaveBeenCalledWith(tree);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* SPDX-FileCopyrightText: 2025 SAP Spartacus team <spartacus-team@sap.com>
*
* SPDX-License-Identifier: Apache-2.0
*/

import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
import { printErrorWithDocsForMigrated_2211_32_To_2211_35 as printErrorWithDocs } from '../fallback-advice-to-follow-docs';

/**
* Moves the `src/assets/` folder to the root and renames it to `public/`,
* to adapt to the new Angular v19 standards.
*/
export function moveAssetsToPublic(): Rule {
return (tree: Tree, context: SchematicContext) => {
enum AssetsDirs {
OLD = 'src/assets',
NEW = 'public',
}

context.logger.info(
`\n⏳ Moving assets folder from "${AssetsDirs.OLD}/" to "${AssetsDirs.NEW}/"...`
);

const sourceDir = tree.getDir(AssetsDirs.OLD);
if (!sourceDir.subfiles.length && !sourceDir.subdirs.length) {
printErrorWithDocs(
`Assets folder not found or empty at ${AssetsDirs.OLD}`,
context
);
return;
}

try {
tree.getDir(AssetsDirs.OLD).visit((filePath) => {
const relativeFilePath = filePath.replace(`${AssetsDirs.OLD}/`, '');
context.logger.info(` ↳ Moving file "${filePath}"`);

const content = tree.read(filePath);
if (content) {
tree.create(`${AssetsDirs.NEW}/${relativeFilePath}`, content);
} else {
printErrorWithDocs(`Failed to read ${filePath} file`, context);
return;
}
});
} catch (error) {
printErrorWithDocs(
`Error moving assets file from "${AssetsDirs.OLD}" to "${AssetsDirs.NEW}". Error: ${error}`,
context
);
}

context.logger.info(` ↳ Deleting old "${AssetsDirs.OLD}/" directory`);
try {
tree.delete(AssetsDirs.OLD);
} catch (error) {
printErrorWithDocs(
`Error deleting old assets directory "${AssetsDirs.OLD}". Error: ${error}`,
context
);
}

context.logger.info(
`✅ Moved assets folder from "${AssetsDirs.OLD}/" to "${AssetsDirs.NEW}/"`
);
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* SPDX-FileCopyrightText: 2025 SAP Spartacus team <spartacus-team@sap.com>
*
* SPDX-License-Identifier: Apache-2.0
*/

import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
import { printErrorWithDocsForMigrated_2211_32_To_2211_35 as printErrorWithDocs } from '../fallback-advice-to-follow-docs';

/**
* Moves the `favicon.ico` file from the `src/` folder to the `public/` folder,
* to adapt to the new Angular v19 standards.
*/
export function moveFaviconToPublic(): Rule {
return (tree: Tree, context: SchematicContext) => {
const fileName = 'favicon.ico';

const oldDir = 'src';
const oldPath = `${oldDir}/${fileName}`;

const newDir = 'public';
const newPath = `${newDir}/${fileName}`;

context.logger.info(
`\n⏳ Moving ${fileName} from "${oldDir}/" to "${newDir}/"...`
);

if (!tree.exists(oldPath)) {
printErrorWithDocs(`Favicon not found at ${oldPath}`, context);
return;
}

const content = tree.read(oldPath);
if (content) {
tree.create(newPath, content);
tree.delete(oldPath);
} else {
printErrorWithDocs(`Failed to read ${oldPath} file`, context);
return;
}

context.logger.info(
`✅ Moved ${fileName} from "${oldDir}/" to "${newDir}/"`
);
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* SPDX-FileCopyrightText: 2025 SAP Spartacus team <spartacus-team@sap.com>
*
* SPDX-License-Identifier: Apache-2.0
*/

import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
import { getWorkspace } from '../../shared/utils/workspace-utils';
import { printErrorWithDocsForMigrated_2211_32_To_2211_35 as printErrorWithDocs } from '../fallback-advice-to-follow-docs';
import {
BrowserBuilderBaseOptions,
BrowserBuilderTarget,
} from '@schematics/angular/utility/workspace-models';

/**
* Updates the Angular configuration file to new Angular v19 standards.
*
* It updates the "assets" property for the "build" and "test" targets,
* to use the new path with the `public/` folder,
* instead of `src/assets` and `src/favicon.ico`.
*/
export function updateAngularJson(): Rule {
return (tree: Tree, context: SchematicContext) => {
context.logger.info('\n⏳ Updating angular.json assets configuration...');

const { workspace, path } = getWorkspace(tree);
const project = workspace.projects[Object.keys(workspace.projects)[0]];

if (!project) {
printErrorWithDocs('No project found in workspace', context);
return;
}

const buildTarget = project.architect?.build as BrowserBuilderTarget;
const testTarget = project.architect?.test;

if (!buildTarget) {
printErrorWithDocs(
'Could not find "build" target in project configuration',
context
);
return;
}

const oldAssets: BrowserBuilderBaseOptions['assets'] = [
'src/favicon.ico',
'src/assets',
];
const newAssets: BrowserBuilderBaseOptions['assets'] = [
{ glob: '**/*', input: 'public' },
];

context.logger.info(
` ↳ Removing "assets" configuration for ${oldAssets
.map((x) => `"${x}"`)
.join(', ')}`
);
context.logger.info(
' ↳ Adding "assets" configuration: `{ glob: "**/*", input: "public" }`'
);

if (Array.isArray(buildTarget.options?.assets)) {
buildTarget.options.assets = buildTarget.options.assets.filter(
(asset: string | object) => !oldAssets.includes(asset)
);

// Add the new public assets config
buildTarget.options.assets = [
...newAssets,
...buildTarget.options.assets,
];
} else {
printErrorWithDocs(
'Could not find "assets" array in "build" target configuration',
context
);
}

// Update config for "test" target
if (Array.isArray(testTarget?.options?.assets)) {
testTarget.options.assets = testTarget.options.assets.filter(
(asset: string | object) => !oldAssets.includes(asset)
);

// Add the new public assets config
testTarget.options.assets = [...newAssets, ...testTarget.options.assets];
} else {
printErrorWithDocs(
'Could not find "assets" array in "test" target configuration',
context
);
}

tree.overwrite(path, JSON.stringify(workspace, null, 2));
context.logger.info('✅ Updated angular.json assets configuration');
};
}
Loading