@angular/build 20.0.0 → 20.1.0-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +19 -19
- package/src/builders/application/options.d.ts +1 -1
- package/src/builders/application/options.js +6 -1
- package/src/builders/application/schema.d.ts +3 -1
- package/src/builders/application/schema.json +2 -2
- package/src/builders/karma/application_builder.js +23 -0
- package/src/builders/karma/schema.d.ts +3 -1
- package/src/builders/karma/schema.json +2 -2
- package/src/builders/unit-test/builder.js +4 -2
- package/src/builders/unit-test/karma-bridge.js +2 -2
- package/src/builders/unit-test/options.d.ts +4 -2
- package/src/builders/unit-test/options.js +9 -3
- package/src/builders/unit-test/schema.d.ts +16 -0
- package/src/builders/unit-test/schema.js +10 -1
- package/src/builders/unit-test/schema.json +23 -1
- package/src/tools/esbuild/loader-import-attribute-plugin.js +1 -1
- package/src/utils/normalize-cache.js +1 -1
- package/src/utils/version.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angular/build",
|
|
3
|
-
"version": "20.0.0",
|
|
3
|
+
"version": "20.1.0-next.0",
|
|
4
4
|
"description": "Official build system for Angular",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Angular CLI",
|
|
@@ -23,11 +23,11 @@
|
|
|
23
23
|
"builders": "builders.json",
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@ampproject/remapping": "2.3.0",
|
|
26
|
-
"@angular-devkit/architect": "0.
|
|
27
|
-
"@babel/core": "7.27.
|
|
28
|
-
"@babel/helper-annotate-as-pure": "7.27.
|
|
26
|
+
"@angular-devkit/architect": "0.2001.0-next.0",
|
|
27
|
+
"@babel/core": "7.27.4",
|
|
28
|
+
"@babel/helper-annotate-as-pure": "7.27.3",
|
|
29
29
|
"@babel/helper-split-export-declaration": "7.24.7",
|
|
30
|
-
"@inquirer/confirm": "5.1.
|
|
30
|
+
"@inquirer/confirm": "5.1.12",
|
|
31
31
|
"@vitejs/plugin-basic-ssl": "2.0.0",
|
|
32
32
|
"beasties": "0.3.4",
|
|
33
33
|
"browserslist": "^4.23.0",
|
|
@@ -41,29 +41,29 @@
|
|
|
41
41
|
"parse5-html-rewriting-stream": "7.1.0",
|
|
42
42
|
"picomatch": "4.0.2",
|
|
43
43
|
"piscina": "5.0.0",
|
|
44
|
-
"rollup": "4.
|
|
45
|
-
"sass": "1.
|
|
44
|
+
"rollup": "4.41.1",
|
|
45
|
+
"sass": "1.89.1",
|
|
46
46
|
"semver": "7.7.2",
|
|
47
47
|
"source-map-support": "0.5.21",
|
|
48
|
-
"tinyglobby": "0.2.
|
|
48
|
+
"tinyglobby": "0.2.14",
|
|
49
49
|
"vite": "6.3.5",
|
|
50
|
-
"watchpack": "2.4.
|
|
50
|
+
"watchpack": "2.4.4"
|
|
51
51
|
},
|
|
52
52
|
"optionalDependencies": {
|
|
53
|
-
"lmdb": "3.
|
|
53
|
+
"lmdb": "3.4.0"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@angular/core": "^20.0.0",
|
|
57
|
-
"@angular/compiler": "^20.0.0",
|
|
58
|
-
"@angular/compiler-cli": "^20.0.0",
|
|
59
|
-
"@angular/localize": "^20.0.0",
|
|
60
|
-
"@angular/platform-browser": "^20.0.0",
|
|
61
|
-
"@angular/platform-server": "^20.0.0",
|
|
62
|
-
"@angular/service-worker": "^20.0.0",
|
|
63
|
-
"@angular/ssr": "^20.0.0",
|
|
56
|
+
"@angular/core": "^20.0.0 || ^20.1.0-next.0",
|
|
57
|
+
"@angular/compiler": "^20.0.0 || ^20.1.0-next.0",
|
|
58
|
+
"@angular/compiler-cli": "^20.0.0 || ^20.1.0-next.0",
|
|
59
|
+
"@angular/localize": "^20.0.0 || ^20.1.0-next.0",
|
|
60
|
+
"@angular/platform-browser": "^20.0.0 || ^20.1.0-next.0",
|
|
61
|
+
"@angular/platform-server": "^20.0.0 || ^20.1.0-next.0",
|
|
62
|
+
"@angular/service-worker": "^20.0.0 || ^20.1.0-next.0",
|
|
63
|
+
"@angular/ssr": "^20.1.0-next.0",
|
|
64
64
|
"karma": "^6.4.0",
|
|
65
65
|
"less": "^4.2.0",
|
|
66
|
-
"ng-packagr": "^20.0.0",
|
|
66
|
+
"ng-packagr": "^20.0.0 || ^20.1.0-next.0",
|
|
67
67
|
"postcss": "^8.4.0",
|
|
68
68
|
"tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0",
|
|
69
69
|
"tslib": "^2.3.0",
|
|
@@ -186,7 +186,7 @@ export declare function normalizeOptions(context: BuilderContext, projectName: s
|
|
|
186
186
|
budgets: import("./schema").Budget[] | undefined;
|
|
187
187
|
publicPath: string | undefined;
|
|
188
188
|
plugins: Plugin[] | undefined;
|
|
189
|
-
loaderExtensions: Record<string, "file" | "binary" | "text"> | undefined;
|
|
189
|
+
loaderExtensions: Record<string, "file" | "base64" | "binary" | "text" | "dataurl"> | undefined;
|
|
190
190
|
jsonLogs: boolean;
|
|
191
191
|
colors: boolean;
|
|
192
192
|
clearScreen: boolean | undefined;
|
|
@@ -96,7 +96,12 @@ async function normalizeOptions(context, projectName, options, extensions) {
|
|
|
96
96
|
if (extension[0] !== '.' || /\.[cm]?[jt]sx?$/.test(extension)) {
|
|
97
97
|
continue;
|
|
98
98
|
}
|
|
99
|
-
if (value !== 'text' &&
|
|
99
|
+
if (value !== 'text' &&
|
|
100
|
+
value !== 'binary' &&
|
|
101
|
+
value !== 'file' &&
|
|
102
|
+
value !== 'dataurl' &&
|
|
103
|
+
value !== 'base64' &&
|
|
104
|
+
value !== 'empty') {
|
|
100
105
|
continue;
|
|
101
106
|
}
|
|
102
107
|
loaderExtensions ??= {};
|
|
@@ -102,7 +102,9 @@ export type Schema = {
|
|
|
102
102
|
* Defines the type of loader to use with a specified file extension when used with a
|
|
103
103
|
* JavaScript `import`. `text` inlines the content as a string; `binary` inlines the content
|
|
104
104
|
* as a Uint8Array; `file` emits the file and provides the runtime location of the file;
|
|
105
|
-
* `
|
|
105
|
+
* `dataurl` inlines the content as a data URL with best guess of MIME type; `base64`
|
|
106
|
+
* inlines the content as a Base64-encoded string; `empty` considers the content to be empty
|
|
107
|
+
* and not include it in bundles.
|
|
106
108
|
*/
|
|
107
109
|
loader?: {
|
|
108
110
|
[key: string]: any;
|
|
@@ -279,10 +279,10 @@
|
|
|
279
279
|
]
|
|
280
280
|
},
|
|
281
281
|
"loader": {
|
|
282
|
-
"description": "Defines the type of loader to use with a specified file extension when used with a JavaScript `import`. `text` inlines the content as a string; `binary` inlines the content as a Uint8Array; `file` emits the file and provides the runtime location of the file; `empty` considers the content to be empty and not include it in bundles.",
|
|
282
|
+
"description": "Defines the type of loader to use with a specified file extension when used with a JavaScript `import`. `text` inlines the content as a string; `binary` inlines the content as a Uint8Array; `file` emits the file and provides the runtime location of the file; `dataurl` inlines the content as a data URL with best guess of MIME type; `base64` inlines the content as a Base64-encoded string; `empty` considers the content to be empty and not include it in bundles.",
|
|
283
283
|
"type": "object",
|
|
284
284
|
"patternProperties": {
|
|
285
|
-
"^\\.\\S+$": { "enum": ["text", "binary", "file", "empty"] }
|
|
285
|
+
"^\\.\\S+$": { "enum": ["text", "binary", "file", "dataurl", "base64", "empty"] }
|
|
286
286
|
}
|
|
287
287
|
},
|
|
288
288
|
"define": {
|
|
@@ -457,6 +457,29 @@ async function initializeApplication(options, context, karmaOptions, transforms
|
|
|
457
457
|
parsedKarmaConfig.plugins.push(AngularPolyfillsPlugin.createPlugin(polyfillsFile, jasmineCleanupFiles));
|
|
458
458
|
parsedKarmaConfig.reporters ??= [];
|
|
459
459
|
parsedKarmaConfig.reporters.push(AngularPolyfillsPlugin.NAME);
|
|
460
|
+
// Adjust karma junit reporter outDir location to maintain previous (devkit) behavior
|
|
461
|
+
// The base path for the reporter was previously the workspace root.
|
|
462
|
+
// To keep the files in the same location, the reporter's output directory is adjusted
|
|
463
|
+
// to be relative to the workspace root when using junit.
|
|
464
|
+
if (parsedKarmaConfig.reporters?.some((reporter) => reporter === 'junit')) {
|
|
465
|
+
if ('junitReporter' in parsedKarmaConfig) {
|
|
466
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
467
|
+
const junitReporterOptions = parsedKarmaConfig['junitReporter'];
|
|
468
|
+
if (junitReporterOptions.outputDir == undefined) {
|
|
469
|
+
junitReporterOptions.outputDir = context.workspaceRoot;
|
|
470
|
+
}
|
|
471
|
+
else if (typeof junitReporterOptions.outputDir === 'string' &&
|
|
472
|
+
!path.isAbsolute(junitReporterOptions.outputDir)) {
|
|
473
|
+
junitReporterOptions.outputDir = path.join(context.workspaceRoot, junitReporterOptions.outputDir);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
478
|
+
parsedKarmaConfig['junitReporter'] = {
|
|
479
|
+
outputDir: context.workspaceRoot,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
}
|
|
460
483
|
// When using code-coverage, auto-add karma-coverage.
|
|
461
484
|
// This was done as part of the karma plugin for webpack.
|
|
462
485
|
if (options.codeCoverage &&
|
|
@@ -65,7 +65,9 @@ export type Schema = {
|
|
|
65
65
|
* Defines the type of loader to use with a specified file extension when used with a
|
|
66
66
|
* JavaScript `import`. `text` inlines the content as a string; `binary` inlines the content
|
|
67
67
|
* as a Uint8Array; `file` emits the file and provides the runtime location of the file;
|
|
68
|
-
* `
|
|
68
|
+
* `dataurl` inlines the content as a data URL with best guess of MIME type; `base64`
|
|
69
|
+
* inlines the content as a Base64-encoded string; `empty` considers the content to be empty
|
|
70
|
+
* and not include it in bundles.
|
|
69
71
|
*/
|
|
70
72
|
loader?: {
|
|
71
73
|
[key: string]: any;
|
|
@@ -163,10 +163,10 @@
|
|
|
163
163
|
"default": []
|
|
164
164
|
},
|
|
165
165
|
"loader": {
|
|
166
|
-
"description": "Defines the type of loader to use with a specified file extension when used with a JavaScript `import`. `text` inlines the content as a string; `binary` inlines the content as a Uint8Array; `file` emits the file and provides the runtime location of the file; `empty` considers the content to be empty and not include it in bundles.",
|
|
166
|
+
"description": "Defines the type of loader to use with a specified file extension when used with a JavaScript `import`. `text` inlines the content as a string; `binary` inlines the content as a Uint8Array; `file` emits the file and provides the runtime location of the file; `dataurl` inlines the content as a data URL with best guess of MIME type; `base64` inlines the content as a Base64-encoded string; `empty` considers the content to be empty and not include it in bundles.",
|
|
167
167
|
"type": "object",
|
|
168
168
|
"patternProperties": {
|
|
169
|
-
"^\\.\\S+$": { "enum": ["text", "binary", "file", "empty"] }
|
|
169
|
+
"^\\.\\S+$": { "enum": ["text", "binary", "file", "dataurl", "base64", "empty"] }
|
|
170
170
|
}
|
|
171
171
|
},
|
|
172
172
|
"define": {
|
|
@@ -84,6 +84,7 @@ async function* execute(options, context, extensions = {}) {
|
|
|
84
84
|
index: false,
|
|
85
85
|
browser: undefined,
|
|
86
86
|
server: undefined,
|
|
87
|
+
outputMode: undefined,
|
|
87
88
|
localize: false,
|
|
88
89
|
budgets: [],
|
|
89
90
|
serviceWorker: false,
|
|
@@ -181,7 +182,8 @@ async function* execute(options, context, extensions = {}) {
|
|
|
181
182
|
browser,
|
|
182
183
|
reporters: normalizedOptions.reporters ?? ['default'],
|
|
183
184
|
coverage: {
|
|
184
|
-
enabled: normalizedOptions.codeCoverage,
|
|
185
|
+
enabled: !!normalizedOptions.codeCoverage,
|
|
186
|
+
reporter: normalizedOptions.codeCoverage?.reporters,
|
|
185
187
|
excludeAfterRemap: true,
|
|
186
188
|
},
|
|
187
189
|
...debugOptions,
|
|
@@ -195,7 +197,7 @@ async function* execute(options, context, extensions = {}) {
|
|
|
195
197
|
// builder's test setup. To workaround this, the excludes are adjusted here to only automatically
|
|
196
198
|
// exclude the TypeScript source test files.
|
|
197
199
|
context.project.config.coverage.exclude = [
|
|
198
|
-
...(normalizedOptions.
|
|
200
|
+
...(normalizedOptions.codeCoverage?.exclude ?? []),
|
|
199
201
|
'**/*.{test,spec}.?(c|m)ts',
|
|
200
202
|
];
|
|
201
203
|
},
|
|
@@ -65,8 +65,8 @@ async function useKarmaBuilder(context, unitTestOptions) {
|
|
|
65
65
|
poll: buildTargetOptions.poll,
|
|
66
66
|
preserveSymlinks: buildTargetOptions.preserveSymlinks,
|
|
67
67
|
browsers: unitTestOptions.browsers?.join(','),
|
|
68
|
-
codeCoverage: unitTestOptions.codeCoverage,
|
|
69
|
-
codeCoverageExclude: unitTestOptions.
|
|
68
|
+
codeCoverage: !!unitTestOptions.codeCoverage,
|
|
69
|
+
codeCoverageExclude: unitTestOptions.codeCoverage?.exclude,
|
|
70
70
|
fileReplacements: buildTargetOptions.fileReplacements,
|
|
71
71
|
reporters: unitTestOptions.reporters,
|
|
72
72
|
webWorkerTsConfig: buildTargetOptions.webWorkerTsConfig,
|
|
@@ -17,8 +17,10 @@ export declare function normalizeOptions(context: BuilderContext, projectName: s
|
|
|
17
17
|
include: string[];
|
|
18
18
|
exclude: string[];
|
|
19
19
|
runnerName: import("./schema").Runner;
|
|
20
|
-
codeCoverage:
|
|
21
|
-
|
|
20
|
+
codeCoverage: {
|
|
21
|
+
exclude: string[] | undefined;
|
|
22
|
+
reporters: [string, Record<string, unknown>][] | undefined;
|
|
23
|
+
} | undefined;
|
|
22
24
|
tsConfig: string;
|
|
23
25
|
reporters: string[] | undefined;
|
|
24
26
|
browsers: string[] | undefined;
|
|
@@ -27,7 +27,7 @@ async function normalizeOptions(context, projectName, options) {
|
|
|
27
27
|
// Target specifier defaults to the current project's build target using a development configuration
|
|
28
28
|
const buildTargetSpecifier = options.buildTarget ?? `::development`;
|
|
29
29
|
const buildTarget = (0, architect_1.targetFromTargetString)(buildTargetSpecifier, projectName, 'build');
|
|
30
|
-
const {
|
|
30
|
+
const { tsConfig, runner, reporters, browsers } = options;
|
|
31
31
|
return {
|
|
32
32
|
// Project/workspace information
|
|
33
33
|
workspaceRoot,
|
|
@@ -39,8 +39,14 @@ async function normalizeOptions(context, projectName, options) {
|
|
|
39
39
|
include: options.include ?? ['**/*.spec.ts'],
|
|
40
40
|
exclude: options.exclude ?? [],
|
|
41
41
|
runnerName: runner,
|
|
42
|
-
codeCoverage
|
|
43
|
-
|
|
42
|
+
codeCoverage: options.codeCoverage
|
|
43
|
+
? {
|
|
44
|
+
exclude: options.codeCoverageExclude,
|
|
45
|
+
reporters: options.codeCoverageReporters?.map((entry) => typeof entry === 'string'
|
|
46
|
+
? [entry, {}]
|
|
47
|
+
: entry),
|
|
48
|
+
}
|
|
49
|
+
: undefined,
|
|
44
50
|
tsConfig,
|
|
45
51
|
reporters,
|
|
46
52
|
browsers,
|
|
@@ -21,6 +21,10 @@ export type Schema = {
|
|
|
21
21
|
* Globs to exclude from code coverage.
|
|
22
22
|
*/
|
|
23
23
|
codeCoverageExclude?: string[];
|
|
24
|
+
/**
|
|
25
|
+
* Reporters to use for code coverage results.
|
|
26
|
+
*/
|
|
27
|
+
codeCoverageReporters?: SchemaCodeCoverageReporter[];
|
|
24
28
|
/**
|
|
25
29
|
* Initialize the test runner to support using the Node Inspector for test debugging.
|
|
26
30
|
*/
|
|
@@ -60,6 +64,18 @@ export type Schema = {
|
|
|
60
64
|
*/
|
|
61
65
|
watch?: boolean;
|
|
62
66
|
};
|
|
67
|
+
export type SchemaCodeCoverageReporter = CodeCoverageReporterCodeCoverageReporter[] | CoverageReporters;
|
|
68
|
+
export type CodeCoverageReporterCodeCoverageReporter = CoverageReporters | {
|
|
69
|
+
[key: string]: any;
|
|
70
|
+
};
|
|
71
|
+
export declare enum CoverageReporters {
|
|
72
|
+
Cobertura = "cobertura",
|
|
73
|
+
Html = "html",
|
|
74
|
+
Lcov = "lcov",
|
|
75
|
+
Lcovonly = "lcovonly",
|
|
76
|
+
Text = "text",
|
|
77
|
+
TextSummary = "text-summary"
|
|
78
|
+
}
|
|
63
79
|
/**
|
|
64
80
|
* The name of the test runner to use for test execution.
|
|
65
81
|
*/
|
|
@@ -2,7 +2,16 @@
|
|
|
2
2
|
// THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE THE
|
|
3
3
|
// CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...).
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
-
exports.Runner = void 0;
|
|
5
|
+
exports.Runner = exports.CoverageReporters = void 0;
|
|
6
|
+
var CoverageReporters;
|
|
7
|
+
(function (CoverageReporters) {
|
|
8
|
+
CoverageReporters["Cobertura"] = "cobertura";
|
|
9
|
+
CoverageReporters["Html"] = "html";
|
|
10
|
+
CoverageReporters["Lcov"] = "lcov";
|
|
11
|
+
CoverageReporters["Lcovonly"] = "lcovonly";
|
|
12
|
+
CoverageReporters["Text"] = "text";
|
|
13
|
+
CoverageReporters["TextSummary"] = "text-summary";
|
|
14
|
+
})(CoverageReporters || (exports.CoverageReporters = CoverageReporters = {}));
|
|
6
15
|
/**
|
|
7
16
|
* The name of the test runner to use for test execution.
|
|
8
17
|
*/
|
|
@@ -64,6 +64,23 @@
|
|
|
64
64
|
},
|
|
65
65
|
"default": []
|
|
66
66
|
},
|
|
67
|
+
"codeCoverageReporters": {
|
|
68
|
+
"type": "array",
|
|
69
|
+
"description": "Reporters to use for code coverage results.",
|
|
70
|
+
"items": {
|
|
71
|
+
"oneOf": [
|
|
72
|
+
{
|
|
73
|
+
"$ref": "#/definitions/coverage-reporters"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"type": "array",
|
|
77
|
+
"minItems": 1,
|
|
78
|
+
"maxItems": 2,
|
|
79
|
+
"items": [{ "$ref": "#/definitions/coverage-reporters" }, { "type": "object" }]
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
},
|
|
67
84
|
"reporters": {
|
|
68
85
|
"type": "array",
|
|
69
86
|
"description": "Test runner reporters to use. Directly passed to the test runner.",
|
|
@@ -78,5 +95,10 @@
|
|
|
78
95
|
}
|
|
79
96
|
},
|
|
80
97
|
"additionalProperties": false,
|
|
81
|
-
"required": ["buildTarget", "tsConfig", "runner"]
|
|
98
|
+
"required": ["buildTarget", "tsConfig", "runner"],
|
|
99
|
+
"definitions": {
|
|
100
|
+
"coverage-reporters": {
|
|
101
|
+
"enum": ["html", "lcov", "lcovonly", "text", "text-summary", "cobertura"]
|
|
102
|
+
}
|
|
103
|
+
}
|
|
82
104
|
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
exports.createLoaderImportAttributePlugin = createLoaderImportAttributePlugin;
|
|
11
11
|
const promises_1 = require("node:fs/promises");
|
|
12
|
-
const SUPPORTED_LOADERS = ['binary', 'file', 'text'];
|
|
12
|
+
const SUPPORTED_LOADERS = ['base64', 'binary', 'dataurl', 'file', 'text'];
|
|
13
13
|
function createLoaderImportAttributePlugin() {
|
|
14
14
|
return {
|
|
15
15
|
name: 'angular-loader-import-attributes',
|
|
@@ -10,7 +10,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
10
10
|
exports.normalizeCacheOptions = normalizeCacheOptions;
|
|
11
11
|
const node_path_1 = require("node:path");
|
|
12
12
|
/** Version placeholder is replaced during the build process with actual package version */
|
|
13
|
-
const VERSION = '20.0.0';
|
|
13
|
+
const VERSION = '20.1.0-next.0';
|
|
14
14
|
function hasCacheMetadata(value) {
|
|
15
15
|
return (!!value &&
|
|
16
16
|
typeof value === 'object' &&
|
package/src/utils/version.js
CHANGED
|
@@ -28,7 +28,7 @@ function assertCompatibleAngularVersion(projectRoot) {
|
|
|
28
28
|
'This likely indicates a corrupted local installation. Please try reinstalling your packages.');
|
|
29
29
|
process.exit(2);
|
|
30
30
|
}
|
|
31
|
-
const supportedAngularSemver = '^20.0.0';
|
|
31
|
+
const supportedAngularSemver = '^20.0.0 || ^20.1.0-next.0';
|
|
32
32
|
if (angularPkgJson['version'] === '0.0.0' || supportedAngularSemver.startsWith('0.0.0')) {
|
|
33
33
|
// Internal CLI and FW testing version.
|
|
34
34
|
return;
|