@ng-native/schematics 0.17.1 → 0.18.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/application/index.cjs +42 -15
- package/application/schema.json +10 -1
- package/files/AGENTS.md +4 -0
- package/files/src/main.ts +5 -3
- package/files/tsconfig.json +1 -1
- package/migrations.json +6 -1
- package/ng-add/index.cjs +1 -1
- package/ng-add/schema.json +6 -0
- package/package.json +3 -2
package/application/index.cjs
CHANGED
|
@@ -27,7 +27,7 @@ function writeJson(tree, file, value) {
|
|
|
27
27
|
* The project's targets. Each is an Expo command run in the project's directory, which is where
|
|
28
28
|
* Expo reads `app.json` and `metro.config.js` from.
|
|
29
29
|
*/
|
|
30
|
-
function targets(root, name) {
|
|
30
|
+
function targets(root, name, lint = false) {
|
|
31
31
|
const expo = (command, args) => ({
|
|
32
32
|
builder: '@ng-native/schematics:expo',
|
|
33
33
|
options: args ? { command, args } : { command },
|
|
@@ -41,6 +41,18 @@ function targets(root, name) {
|
|
|
41
41
|
'run-android': expo('run:android'),
|
|
42
42
|
test: { builder: '@ng-native/schematics:vitest', options: {} },
|
|
43
43
|
typecheck: { builder: '@ng-native/schematics:typecheck', options: {} },
|
|
44
|
+
// With `--linter=eslint`, angular-eslint's builder, which `ng lint` runs, with the project's
|
|
45
|
+
// own config: the rules that know a device from a browser, which a workspace's config for its
|
|
46
|
+
// web apps does not.
|
|
47
|
+
...(lint && {
|
|
48
|
+
lint: {
|
|
49
|
+
builder: '@angular-eslint/builder:lint',
|
|
50
|
+
options: {
|
|
51
|
+
eslintConfig: path.join(root, 'eslint.config.mjs'),
|
|
52
|
+
lintFilePatterns: [path.join(root, '**/*.ts'), path.join(root, '**/*.html')],
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
}),
|
|
44
56
|
};
|
|
45
57
|
}
|
|
46
58
|
|
|
@@ -59,7 +71,7 @@ const WEB_SPECS = [
|
|
|
59
71
|
'service',
|
|
60
72
|
];
|
|
61
73
|
|
|
62
|
-
function project(root, name, prefix) {
|
|
74
|
+
function project(root, name, prefix, lint) {
|
|
63
75
|
return {
|
|
64
76
|
projectType: 'application',
|
|
65
77
|
root,
|
|
@@ -73,18 +85,19 @@ function project(root, name, prefix) {
|
|
|
73
85
|
schematics: Object.fromEntries(
|
|
74
86
|
WEB_SPECS.map((schematic) => [`@schematics/angular:${schematic}`, { skipTests: true }]),
|
|
75
87
|
),
|
|
76
|
-
architect: targets(root, name),
|
|
88
|
+
architect: targets(root, name, lint),
|
|
77
89
|
};
|
|
78
90
|
}
|
|
79
91
|
|
|
80
92
|
/** The Commands section of the app's `AGENTS.md`, as this workspace runs them. */
|
|
81
|
-
function commands(name) {
|
|
93
|
+
function commands(name, lint) {
|
|
82
94
|
const rows = [
|
|
83
95
|
[`ng serve ${name}`, 'Metro; press i or a for a simulator, or scan the QR code with Expo Go'],
|
|
84
96
|
[`ng run ${name}:run-ios`, 'the iOS simulator'],
|
|
85
97
|
[`ng run ${name}:run-android`, 'the Android emulator'],
|
|
86
98
|
[`ng test ${name}`, 'Vitest, in Node against a fake native layer: no simulator needed'],
|
|
87
99
|
[`ng run ${name}:typecheck`, 'ngc, which checks the templates as well as the code'],
|
|
100
|
+
...(lint ? [[`ng lint ${name}`, 'ESLint: what compiles but does nothing on a device']] : []),
|
|
88
101
|
[`ng build ${name}`, 'an exported bundle'],
|
|
89
102
|
];
|
|
90
103
|
const width = Math.max(...rows.map(([command]) => command.length)) + 2;
|
|
@@ -94,16 +107,19 @@ function commands(name) {
|
|
|
94
107
|
'```',
|
|
95
108
|
'',
|
|
96
109
|
'`src/main.ts` mounts the root component, `src/app/app.ts`. ' +
|
|
97
|
-
|
|
110
|
+
(lint
|
|
111
|
+
? `Run \`ng test ${name}\` and \`ng lint ${name}\` after a change; both are fast.`
|
|
112
|
+
: `Run \`ng test ${name}\` after a change; it is fast.`),
|
|
98
113
|
].join('\n');
|
|
99
114
|
}
|
|
100
115
|
|
|
101
|
-
function writeFiles(tree, root, name, bundleIdentifier) {
|
|
116
|
+
function writeFiles(tree, root, name, bundleIdentifier, lint) {
|
|
102
117
|
for (const file of native.SOURCE_FILES) {
|
|
103
118
|
tree.create(path.join(root, file), native.sourceFile(file));
|
|
104
119
|
}
|
|
105
120
|
tree.create(path.join(root, 'app.json'), native.appJson(name, bundleIdentifier));
|
|
106
|
-
tree.create(path.join(root, 'AGENTS.md'), native.agentsFile(commands(name)));
|
|
121
|
+
tree.create(path.join(root, 'AGENTS.md'), native.agentsFile(commands(name, lint)));
|
|
122
|
+
if (lint) tree.create(path.join(root, 'eslint.config.mjs'), native.ESLINT_CONFIG);
|
|
107
123
|
tree.create(path.join(root, 'CLAUDE.md'), '@AGENTS.md\n');
|
|
108
124
|
tree.create(path.join(root, '.gitignore'), native.GITIGNORE);
|
|
109
125
|
const manifest = native.projectManifest(name, readJson(tree, 'package.json'));
|
|
@@ -111,12 +127,19 @@ function writeFiles(tree, root, name, bundleIdentifier) {
|
|
|
111
127
|
}
|
|
112
128
|
|
|
113
129
|
/** Adds what is missing and leaves every version the workspace already chose alone. */
|
|
114
|
-
function addDependencies(tree, context) {
|
|
130
|
+
function addDependencies(tree, context, lint) {
|
|
115
131
|
const manifest = readJson(tree, 'package.json');
|
|
116
|
-
|
|
132
|
+
const linter = lint ? 'eslint' : 'none';
|
|
133
|
+
for (const problem of native.conflicts(manifest, { linter })) context.logger.warn(problem);
|
|
134
|
+
const wanted = {
|
|
135
|
+
dependencies: native.dependencies,
|
|
136
|
+
devDependencies: lint
|
|
137
|
+
? { ...native.devDependencies, ...native.lintDevDependencies }
|
|
138
|
+
: native.devDependencies,
|
|
139
|
+
};
|
|
117
140
|
for (const field of ['dependencies', 'devDependencies']) {
|
|
118
141
|
const existing = { ...manifest.dependencies, ...manifest.devDependencies };
|
|
119
|
-
const added = Object.entries(
|
|
142
|
+
const added = Object.entries(wanted[field]).filter(([name]) => !(name in existing));
|
|
120
143
|
manifest[field] = Object.fromEntries(
|
|
121
144
|
[...Object.entries(manifest[field] ?? {}), ...added].sort(([a], [b]) => (a < b ? -1 : 1)),
|
|
122
145
|
);
|
|
@@ -133,7 +156,7 @@ function ignoreExpo(tree) {
|
|
|
133
156
|
}
|
|
134
157
|
|
|
135
158
|
/**
|
|
136
|
-
* @param {{ name: string, directory?: string, prefix?: string, bundleIdentifier?: string, skipInstall?: boolean }} options
|
|
159
|
+
* @param {{ name: string, directory?: string, prefix?: string, bundleIdentifier?: string, linter?: 'eslint' | 'none', skipInstall?: boolean }} options
|
|
137
160
|
*/
|
|
138
161
|
function application(options) {
|
|
139
162
|
return (tree, context) => {
|
|
@@ -154,16 +177,20 @@ function application(options) {
|
|
|
154
177
|
const problem = native.bundleIdentifierProblem(bundleIdentifier);
|
|
155
178
|
if (problem) throw new SchematicsException(problem);
|
|
156
179
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
180
|
+
const lint = options.linter === 'eslint';
|
|
181
|
+
addDependencies(tree, context, lint);
|
|
182
|
+
writeFiles(tree, root, name, bundleIdentifier, lint);
|
|
183
|
+
const added = project(root, name, options.prefix, lint);
|
|
184
|
+
workspace.projects = { ...workspace.projects, [name]: added };
|
|
160
185
|
writeJson(tree, 'angular.json', workspace);
|
|
161
186
|
ignoreExpo(tree);
|
|
162
187
|
|
|
163
188
|
if (!options.skipInstall) context.addTask(new NodePackageInstallTask());
|
|
164
189
|
context.logger.info(
|
|
165
190
|
`Created ${root}. Run it with: ng serve ${name} (Metro), ng run ${name}:run-ios, ` +
|
|
166
|
-
`ng test ${name}, ng run ${name}:typecheck,
|
|
191
|
+
`ng test ${name}, ng run ${name}:typecheck, ` +
|
|
192
|
+
(lint ? `ng lint ${name}, ` : '') +
|
|
193
|
+
`ng build ${name}.`,
|
|
167
194
|
);
|
|
168
195
|
};
|
|
169
196
|
}
|
package/application/schema.json
CHANGED
|
@@ -8,7 +8,10 @@
|
|
|
8
8
|
"type": "string",
|
|
9
9
|
"description": "The project name, in angular.json and as the Expo slug.",
|
|
10
10
|
"pattern": "^[a-z][a-z0-9-]*$",
|
|
11
|
-
"$default": {
|
|
11
|
+
"$default": {
|
|
12
|
+
"$source": "argv",
|
|
13
|
+
"index": 0
|
|
14
|
+
},
|
|
12
15
|
"x-prompt": "What should the native app be called?"
|
|
13
16
|
},
|
|
14
17
|
"directory": {
|
|
@@ -25,6 +28,12 @@
|
|
|
25
28
|
"type": "string",
|
|
26
29
|
"description": "The iOS bundle identifier and Android package: com.acme.mobile. Defaults to com.<workspace scope>.<name>."
|
|
27
30
|
},
|
|
31
|
+
"linter": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"description": "eslint adds an eslint.config.mjs with @ng-native/eslint-plugin's recommended rules, its dependencies and a lint target.",
|
|
34
|
+
"enum": ["eslint", "none"],
|
|
35
|
+
"default": "none"
|
|
36
|
+
},
|
|
28
37
|
"skipInstall": {
|
|
29
38
|
"type": "boolean",
|
|
30
39
|
"description": "Write the files and package.json without installing.",
|
package/files/AGENTS.md
CHANGED
|
@@ -83,3 +83,7 @@ change; both are fast.
|
|
|
83
83
|
`getByText` or `getByTestId` (which matches `nativeID` and `testID`), and act with
|
|
84
84
|
`userEvent.setup().press(...)` or `.type(...)`, all from `@ng-native/testing`. Tests run in Node
|
|
85
85
|
with no simulator, so write one alongside a change.
|
|
86
|
+
|
|
87
|
+
A service that depends on the platform injects `OS` and `OS_VERSION` from `@ng-native/device`
|
|
88
|
+
rather than importing `Platform` from `react-native`, which a test cannot load. A test provides
|
|
89
|
+
the values it wants: `{ provide: OS, useValue: 'android' }`.
|
package/files/src/main.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { App } from './app/app.ts';
|
|
|
10
10
|
|
|
11
11
|
registerPlatformComponents(Platform.OS);
|
|
12
12
|
|
|
13
|
-
AppRegistry.registerRunnable('main', ({ rootTag }
|
|
13
|
+
AppRegistry.registerRunnable('main', ({ rootTag }) => {
|
|
14
14
|
const app = mount(Number(rootTag), App, getFabricUIManager(), {
|
|
15
15
|
// Colours, as the integers the platform wants.
|
|
16
16
|
processColor,
|
|
@@ -26,6 +26,8 @@ AppRegistry.registerRunnable('main', ({ rootTag }: { rootTag: number | string })
|
|
|
26
26
|
|
|
27
27
|
// Re-resolves the conditions when the device rotates or the theme changes. A rotation dirties
|
|
28
28
|
// no component and no binding, so without this nothing re-renders and `dark:` stops following
|
|
29
|
-
// the system switch.
|
|
30
|
-
|
|
29
|
+
// the system switch. It stops with the app, which Android destroys when it recreates the
|
|
30
|
+
// activity, so the app mounted in its place is the only one still watching.
|
|
31
|
+
const stopWatching = watchConditions(app.engine);
|
|
32
|
+
app.applicationRef.onDestroy(stopWatching);
|
|
31
33
|
});
|
package/files/tsconfig.json
CHANGED
package/migrations.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "../../node_modules/@angular-devkit/schematics/collection-schema.json",
|
|
3
3
|
"schematics": {
|
|
4
4
|
"sync-app-versions": {
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.18.0",
|
|
6
6
|
"description": "Move the @ng-native packages each project's own package.json lists to the new version.",
|
|
7
7
|
"factory": "./migrations/run.cjs#sync-app-versions"
|
|
8
8
|
},
|
|
@@ -40,6 +40,11 @@
|
|
|
40
40
|
"version": "0.17.1",
|
|
41
41
|
"description": "Give each Angular Native project in angular.json a typecheck target, run by ngc.",
|
|
42
42
|
"factory": "./migrations/run.cjs#native-typecheck-target"
|
|
43
|
+
},
|
|
44
|
+
"stop-watching-conditions": {
|
|
45
|
+
"version": "0.17.2",
|
|
46
|
+
"description": "Keep what watchConditions returns in each app's entry point and stop it as the app is destroyed.",
|
|
47
|
+
"factory": "./migrations/run.cjs#stop-watching-conditions"
|
|
43
48
|
}
|
|
44
49
|
}
|
|
45
50
|
}
|
package/ng-add/index.cjs
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
const { application } = require('../application/index.cjs');
|
|
10
10
|
|
|
11
|
-
/** @param {{ name?: string, directory?: string, bundleIdentifier?: string, skipInstall?: boolean }} options */
|
|
11
|
+
/** @param {{ name?: string, directory?: string, bundleIdentifier?: string, linter?: 'eslint' | 'none', skipInstall?: boolean }} options */
|
|
12
12
|
function ngAdd(options) {
|
|
13
13
|
return application({ ...options, name: options.name ?? 'native', prefix: 'app' });
|
|
14
14
|
}
|
package/ng-add/schema.json
CHANGED
|
@@ -18,6 +18,12 @@
|
|
|
18
18
|
"type": "string",
|
|
19
19
|
"description": "The iOS bundle identifier and Android package: com.acme.mobile. Defaults to com.<workspace scope>.<name>."
|
|
20
20
|
},
|
|
21
|
+
"linter": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "eslint adds an eslint.config.mjs with @ng-native/eslint-plugin's recommended rules, its dependencies and a lint target.",
|
|
24
|
+
"enum": ["eslint", "none"],
|
|
25
|
+
"default": "none"
|
|
26
|
+
},
|
|
21
27
|
"skipInstall": {
|
|
22
28
|
"type": "boolean",
|
|
23
29
|
"description": "Write the files and package.json without installing.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ng-native/schematics",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "ng add and ng generate for Angular Native: an Expo app in an Angular CLI workspace, with builders that run it.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"angular",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"@ng-native/analog",
|
|
29
29
|
"@ng-native/components",
|
|
30
30
|
"@ng-native/device",
|
|
31
|
+
"@ng-native/eslint-plugin",
|
|
31
32
|
"@ng-native/expo",
|
|
32
33
|
"@ng-native/fabric",
|
|
33
34
|
"@ng-native/icons",
|
|
@@ -47,7 +48,7 @@
|
|
|
47
48
|
"@angular-devkit/architect": ">=0.2200.0 <0.2300.0",
|
|
48
49
|
"@angular-devkit/core": "^22.0.0",
|
|
49
50
|
"@angular-devkit/schematics": "^22.0.0",
|
|
50
|
-
"@ng-native/migrate": "0.
|
|
51
|
+
"@ng-native/migrate": "0.18.0"
|
|
51
52
|
},
|
|
52
53
|
"devDependencies": {
|
|
53
54
|
"@schematics/angular": "22.1.9"
|