Skip to content

Commit e35e4e4

Browse files
Merge pull request #790 from NordicSemiconductor/enhance/check_package_json_with_zod
Check `package.json` with zod
2 parents 6662019 + 4a0f720 commit e35e4e4

21 files changed

+375
-244
lines changed

Changelog.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,22 @@ This project does _not_ adhere to
77
[Semantic Versioning](https://semver.org/spec/v2.0.0.html) but contrary to it
88
every new version is a new major version.
99

10+
## 118 - 2023-10-13
11+
12+
### Added
13+
14+
- Option to configure external React to support legacy apps in the launcher.
15+
16+
### Changed
17+
18+
- Checking whether the `package.json` contains all required fields is now also
19+
down before building.
20+
- Use `zod` for the `package.json` schema for apps. This is used verify that
21+
apps have the right fields in `package.json` and also generates the right
22+
TypeScript type for it.
23+
- `nrfConnectForDesktop.html` is not optional anymore, it must always be
24+
specified in `package.json`.
25+
1026
## 117 - 2023-10-04
1127

1228
### Fixed
@@ -394,7 +410,7 @@ declare module '!!@svgr!*.svg' {
394410
### Steps to upgrade when using this package
395411

396412
- Following this step will make the app incompatible with the currently
397-
released launcher (v4.1.2): Add an `index`-property to package.json. The
413+
released launcher (v4.1.2): Add an `html`-property to package.json. The
398414
following example will use the index bundled with shared that works with our
399415
apps, but apps can also make their own instead if needed.
400416

ipc/MetaFiles.ts

Lines changed: 15 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* SPDX-License-Identifier: LicenseRef-Nordic-4-Clause
55
*/
66

7-
import { DevicePCA } from '../src/Device/deviceInfo/deviceInfo';
7+
import { z } from 'zod';
88

99
export type UrlString = string;
1010

@@ -40,49 +40,21 @@ export interface AppInfo {
4040
};
4141
}
4242

43-
interface ObjectContainingOptionalStrings {
44-
[index: string]: string | undefined;
45-
}
43+
export const semver = z.string().regex(
44+
// From https://semver.org
45+
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/,
46+
'Is not a valid string for a semantic version'
47+
);
4648

47-
interface NrfConnectForDesktop {
48-
supportedDevices?: DevicePCA[];
49-
nrfutil?: NrfutilModules;
50-
html?: string;
51-
}
49+
const nrfutilModuleName = z.string();
50+
const nrfutilModuleVersion = semver;
5251

53-
type SemverString = string;
52+
export type NrfutilModuleName = z.infer<typeof nrfutilModuleName>;
53+
export type NrfutilModuleVersion = z.infer<typeof nrfutilModuleVersion>;
5454

55-
export type NrfutilModuleName = string;
56-
export type NrfutilModuleVersion = SemverString;
55+
export const nrfModules = z.record(
56+
nrfutilModuleName,
57+
nrfutilModuleVersion.array().nonempty()
58+
);
5759

58-
export interface NrfutilModules {
59-
[name: NrfutilModuleName]: [
60-
NrfutilModuleVersion,
61-
...NrfutilModuleVersion[]
62-
];
63-
}
64-
65-
export interface PackageJson {
66-
name: string;
67-
version: SemverString;
68-
69-
// Several optional properties
70-
author?: string;
71-
bin?: ObjectContainingOptionalStrings | string;
72-
dependencies?: ObjectContainingOptionalStrings;
73-
description?: string;
74-
homepage?: UrlString;
75-
devDependencies?: ObjectContainingOptionalStrings;
76-
displayName?: string;
77-
engines?: ObjectContainingOptionalStrings;
78-
nrfConnectForDesktop?: NrfConnectForDesktop;
79-
files?: readonly string[];
80-
license?: string;
81-
main?: string;
82-
peerDependencies?: ObjectContainingOptionalStrings;
83-
repository?: {
84-
type: string;
85-
url: UrlString;
86-
};
87-
scripts?: ObjectContainingOptionalStrings;
88-
}
60+
export type NrfutilModules = z.infer<typeof nrfModules>;

ipc/device.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright (c) 2023 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: LicenseRef-Nordic-4-Clause
5+
*/
6+
7+
export const knownDevicePcas = [
8+
'PCA10028',
9+
'PCA10031',
10+
'PCA10040',
11+
'PCA10056',
12+
'PCA10059',
13+
'PCA10090',
14+
'PCA10095',
15+
'PCA10100',
16+
'PCA10121',
17+
'PCA20020',
18+
'PCA20035',
19+
'PCA10143',
20+
'PCA10152',
21+
'PCA10153',
22+
'PCA20049',
23+
] as const;
24+
25+
export type KnownDevicePCA = (typeof knownDevicePcas)[number];

ipc/schema/packageJson.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright (c) 2023 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: LicenseRef-Nordic-4-Clause
5+
*/
6+
7+
import { z } from 'zod';
8+
9+
import { knownDevicePcas } from '../device';
10+
import { nrfModules, semver } from '../MetaFiles';
11+
import { parseWithPrettifiedErrorMessage } from './parseJson';
12+
13+
const nrfConnectForDesktop = z.object({
14+
supportedDevices: z.enum(knownDevicePcas).array().nonempty().optional(),
15+
nrfutil: nrfModules.optional(),
16+
html: z.string(),
17+
});
18+
19+
const recordOfOptionalStrings = z.record(z.string().optional());
20+
21+
const engines = recordOfOptionalStrings.and(
22+
z.object({ nrfconnect: z.string() })
23+
);
24+
25+
const packageJson = z.object({
26+
name: z.string(),
27+
version: semver,
28+
29+
author: z.string().optional(),
30+
bin: z.string().or(recordOfOptionalStrings).optional(),
31+
dependencies: recordOfOptionalStrings.optional(),
32+
description: z.string(),
33+
homepage: z.string().url().optional(),
34+
devDependencies: recordOfOptionalStrings.optional(),
35+
displayName: z.string(),
36+
engines,
37+
nrfConnectForDesktop,
38+
files: z.string().array().optional(),
39+
license: z.string().optional(),
40+
main: z.string().optional(),
41+
peerDependencies: recordOfOptionalStrings.optional(),
42+
repository: z
43+
.object({
44+
type: z.string(),
45+
url: z.string().url(),
46+
})
47+
.optional(),
48+
scripts: recordOfOptionalStrings.optional(),
49+
});
50+
51+
export type PackageJson = z.infer<typeof packageJson>;
52+
53+
export const parsePackageJson =
54+
parseWithPrettifiedErrorMessage<PackageJson>(packageJson);
55+
56+
// In the launcher we want to handle that the whole nrfConnectForDesktop may be missing
57+
// and the html in it can also be undefined, so there we need to use this legacy variant
58+
const legacyPackageJson = packageJson.merge(
59+
z.object({
60+
nrfConnectForDesktop: nrfConnectForDesktop
61+
.partial({ html: true })
62+
.optional(),
63+
})
64+
);
65+
66+
export type LegacyPackageJson = z.infer<typeof legacyPackageJson>;
67+
68+
export const parseLegacyPackageJson =
69+
parseWithPrettifiedErrorMessage<LegacyPackageJson>(legacyPackageJson);

ipc/schema/parseJson.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright (c) 2023 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: LicenseRef-Nordic-4-Clause
5+
*/
6+
7+
import { z } from 'zod';
8+
import { fromZodError } from 'zod-validation-error';
9+
10+
export const parseWithPrettifiedErrorMessage =
11+
<Out, T extends z.ZodType<Out> = z.ZodTypeAny>(schema: T) =>
12+
(content: string) => {
13+
const result = schema.safeParse(JSON.parse(content));
14+
15+
if (result.success) {
16+
return result;
17+
}
18+
19+
return {
20+
...result,
21+
error: fromZodError(result.error, {
22+
prefix: 'Error in package.json',
23+
prefixSeparator: ':\n- ',
24+
issueSeparator: '\n- ',
25+
}),
26+
};
27+
};

main/index.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,20 @@ export const serialPort = {
2828
forRenderer: forRendererSerialPort,
2929
};
3030

31-
export * from '../ipc/MetaFiles';
31+
export {
32+
type AppInfo,
33+
type NrfutilModuleName,
34+
type NrfutilModules,
35+
type NrfutilModuleVersion,
36+
type SourceJson,
37+
type WithdrawnJson,
38+
} from '../ipc/MetaFiles';
39+
export {
40+
type LegacyPackageJson,
41+
type PackageJson,
42+
parseLegacyPackageJson,
43+
parsePackageJson,
44+
} from '../ipc/schema/packageJson';
3245

3346
export { type OverwriteOptions } from '../ipc/serialPort';
3447
export { type OpenAppOptions } from '../ipc/openWindow';

package-lock.json

Lines changed: 35 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@nordicsemiconductor/pc-nrfconnect-shared",
3-
"version": "117.0.0",
3+
"version": "118.0.0",
44
"description": "Shared commodities for developing pc-nrfconnect-* packages",
55
"repository": {
66
"type": "git",
@@ -12,7 +12,7 @@
1212
"check-for-typescript": "./scripts/check-for-typescript.ts",
1313
"check-app-properties": "./scripts/check-app-properties.ts",
1414
"nrfconnect-license": "./scripts/nrfconnect-license.ts",
15-
"run-esbuild": "./scripts/esbuild.js"
15+
"run-esbuild": "./scripts/esbuild.ts"
1616
},
1717
"main": "src",
1818
"scripts": {
@@ -119,7 +119,9 @@
119119
"util": "0.12.5",
120120
"uuid": "8.3.2",
121121
"winston": "3.8.2",
122-
"xterm-headless": "^5.3.0"
122+
"xterm-headless": "^5.3.0",
123+
"zod": "^3.22.2",
124+
"zod-validation-error": "^1.5.0"
123125
},
124126
"typings": "./typings/generated/src/index.d.ts",
125127
"eslintConfig": {

0 commit comments

Comments
 (0)