@ng-native/nx 0.1.3 → 0.3.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/README.md +8 -3
- package/application/index.cjs +52 -14
- package/component/index.cjs +66 -0
- package/component/schema.json +38 -0
- package/component/sources.cjs +47 -0
- package/files/AGENTS.md +3 -0
- package/files/src/app/app.ts +22 -4
- package/files/src/main.ts +4 -0
- package/generators.json +22 -0
- package/init/index.cjs +91 -1
- package/library/index.cjs +239 -0
- package/library/schema.json +36 -0
- package/migrations/run.cjs +5 -0
- package/migrations.json +24 -0
- package/native-app.cjs +19 -171
- package/package.json +27 -4
- package/sync/index.cjs +233 -0
- package/sync/schema.json +8 -0
- package/tailwind/index.cjs +573 -0
- package/tailwind/schema.json +37 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nx g @ng-native/nx:library packages/ui`: a library of native components, tested the way an
|
|
3
|
+
* Angular Native app is.
|
|
4
|
+
*
|
|
5
|
+
* The library itself is the workspace's own kind: `@nx/angular:library` where the workspace has
|
|
6
|
+
* `@nx/angular`, and `@nx/js:library` in the TypeScript preset, which cannot take `@nx/angular`
|
|
7
|
+
* because Angular does not support TypeScript project references. Either is run without its
|
|
8
|
+
* tests, which would be Analog on jsdom or a plain Vitest, neither of which can render a native
|
|
9
|
+
* component. In their place this writes the app's `vitest.config.mts`, a `test` target that runs
|
|
10
|
+
* it once, a `tsconfig.spec.json` for the tests, and a native component with a test for the fake
|
|
11
|
+
* Fabric in place of the one the generator wrote.
|
|
12
|
+
*/
|
|
13
|
+
const { readFileSync } = require('node:fs');
|
|
14
|
+
const nodePath = require('node:path');
|
|
15
|
+
const path = nodePath.posix;
|
|
16
|
+
const {
|
|
17
|
+
addDependenciesToPackageJson,
|
|
18
|
+
ensurePackage,
|
|
19
|
+
formatFiles,
|
|
20
|
+
getProjects,
|
|
21
|
+
installPackagesTask,
|
|
22
|
+
joinPathFragments,
|
|
23
|
+
names,
|
|
24
|
+
readNxJson,
|
|
25
|
+
readJson,
|
|
26
|
+
updateJson,
|
|
27
|
+
updateProjectConfiguration,
|
|
28
|
+
} = require('@nx/devkit');
|
|
29
|
+
const native = require('../native-app.cjs');
|
|
30
|
+
const { workspaceNxVersion } = require('../init/index.cjs');
|
|
31
|
+
const { hasPathAliases } = require('../application/index.cjs');
|
|
32
|
+
const { usesWorkspaces } = require('../application/workspaces.cjs');
|
|
33
|
+
const { asSaved } = require('../save-exact.cjs');
|
|
34
|
+
const { writeComponent } = require('../component/index.cjs');
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The defaults a generator's schema gives its options, from the copy installed at `root`.
|
|
38
|
+
*
|
|
39
|
+
* @param {string} root
|
|
40
|
+
* @param {string} pkg
|
|
41
|
+
* @param {string} generator
|
|
42
|
+
* @returns {Record<string, unknown>}
|
|
43
|
+
*/
|
|
44
|
+
function schemaDefaults(root, pkg, generator) {
|
|
45
|
+
let manifest;
|
|
46
|
+
try {
|
|
47
|
+
manifest = require.resolve(`${pkg}/package.json`, { paths: [root] });
|
|
48
|
+
} catch {
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
const read = (file) =>
|
|
52
|
+
JSON.parse(readFileSync(nodePath.join(nodePath.dirname(manifest), file), 'utf8'));
|
|
53
|
+
const entry = read(read('package.json').generators).generators[generator];
|
|
54
|
+
const { properties = {} } = read(entry.schema);
|
|
55
|
+
return Object.fromEntries(
|
|
56
|
+
Object.entries(properties)
|
|
57
|
+
.filter(([, property]) => 'default' in property)
|
|
58
|
+
.map(([name, property]) => [name, property.default]),
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The options `nx g <pkg>:<generator>` starts from: its schema's defaults, then the workspace's
|
|
64
|
+
* own in `nx.json`. A generator called from another gets neither, and `@nx/angular:library` called
|
|
65
|
+
* bare writes a library with strict type checking off.
|
|
66
|
+
*
|
|
67
|
+
* @param {import('@nx/devkit').Tree} tree
|
|
68
|
+
* @param {string} pkg
|
|
69
|
+
* @param {string} generator
|
|
70
|
+
*/
|
|
71
|
+
function generatorDefaults(tree, pkg, generator, root = tree.root) {
|
|
72
|
+
const workspace = readNxJson(tree)?.generators ?? {};
|
|
73
|
+
return {
|
|
74
|
+
...schemaDefaults(root, pkg, generator),
|
|
75
|
+
...workspace[pkg]?.[generator],
|
|
76
|
+
...workspace[`${pkg}:${generator}`],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The library generators this one builds on, at the workspace's Nx. Replaced in tests. */
|
|
81
|
+
const bases = {
|
|
82
|
+
/** @param {import('@nx/devkit').Tree} tree @param {object} options */
|
|
83
|
+
async angular(tree, options) {
|
|
84
|
+
// Only run where the workspace lists `@nx/angular`, whose root export has no generators.
|
|
85
|
+
const { libraryGenerator } = ensurePackage('@nx/angular/generators', workspaceNxVersion(tree));
|
|
86
|
+
return libraryGenerator(tree, {
|
|
87
|
+
...generatorDefaults(tree, '@nx/angular', 'library'),
|
|
88
|
+
...options,
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
/** @param {import('@nx/devkit').Tree} tree @param {object} options */
|
|
92
|
+
async js(tree, options) {
|
|
93
|
+
const { libraryGenerator } = ensurePackage('@nx/js', workspaceNxVersion(tree));
|
|
94
|
+
return libraryGenerator(tree, { ...generatorDefaults(tree, '@nx/js', 'library'), ...options });
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/** What the component and its test import. */
|
|
99
|
+
const imports = {
|
|
100
|
+
dependencies: {
|
|
101
|
+
'@angular/core': native.dependencies['@angular/core'],
|
|
102
|
+
'@ng-native/components': native.dependencies['@ng-native/components'],
|
|
103
|
+
},
|
|
104
|
+
devDependencies: {
|
|
105
|
+
'@ng-native/testing': native.devDependencies['@ng-native/testing'],
|
|
106
|
+
vitest: native.devDependencies.vitest,
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
function projectAt(tree, root) {
|
|
111
|
+
for (const [name, project] of getProjects(tree))
|
|
112
|
+
if (project.root === root) return { name, project };
|
|
113
|
+
throw new Error(`No project was generated in ${root}.`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The native component and its test, where the generator put its own component: in a folder of
|
|
118
|
+
* its own under `src/lib` from `@nx/angular`, with an external template and stylesheet this
|
|
119
|
+
* leaves out, or as `src/lib/<name>.ts` from `@nx/js`. `src/index.ts` already exports it.
|
|
120
|
+
*/
|
|
121
|
+
function replaceComponent(tree, project, file) {
|
|
122
|
+
const lib = joinPathFragments(project.root, 'src', 'lib');
|
|
123
|
+
const folder = tree.exists(joinPathFragments(lib, file, `${file}.ts`))
|
|
124
|
+
? joinPathFragments(lib, file)
|
|
125
|
+
: lib;
|
|
126
|
+
for (const extension of ['html', 'css', 'spec.ts']) {
|
|
127
|
+
const own = joinPathFragments(folder, `${file}.${extension}`);
|
|
128
|
+
if (tree.exists(own)) tree.delete(own);
|
|
129
|
+
}
|
|
130
|
+
writeComponent(tree, folder, file, project.prefix);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* A `tsconfig.spec.json` like the one each generator writes with its tests, including the tests
|
|
135
|
+
* and the Vitest config. The tests import their component as `./ui.ts`, as the template's do.
|
|
136
|
+
*/
|
|
137
|
+
function writeSpecConfig(tree, root, angular) {
|
|
138
|
+
const include = ['vitest.config.mts', 'src/**/*.test.ts'];
|
|
139
|
+
const spec = angular
|
|
140
|
+
? {
|
|
141
|
+
extends: './tsconfig.json',
|
|
142
|
+
compilerOptions: { noEmit: true, allowImportingTsExtensions: true },
|
|
143
|
+
include,
|
|
144
|
+
}
|
|
145
|
+
: {
|
|
146
|
+
// A project reference of the TypeScript preset's, which emits declarations only.
|
|
147
|
+
extends: readJson(tree, joinPathFragments(root, 'tsconfig.lib.json')).extends,
|
|
148
|
+
compilerOptions: { outDir: './out-tsc/vitest', allowImportingTsExtensions: true },
|
|
149
|
+
include,
|
|
150
|
+
references: [{ path: './tsconfig.lib.json' }],
|
|
151
|
+
};
|
|
152
|
+
tree.write(joinPathFragments(root, 'tsconfig.spec.json'), JSON.stringify(spec, null, 2) + '\n');
|
|
153
|
+
updateJson(tree, joinPathFragments(root, 'tsconfig.json'), (config) => {
|
|
154
|
+
const references = config.references ?? [];
|
|
155
|
+
if (!references.some((reference) => reference.path === './tsconfig.spec.json')) {
|
|
156
|
+
references.push({ path: './tsconfig.spec.json' });
|
|
157
|
+
}
|
|
158
|
+
return { ...config, references };
|
|
159
|
+
});
|
|
160
|
+
updateJson(tree, joinPathFragments(root, 'tsconfig.lib.json'), (config) => {
|
|
161
|
+
const exclude = config.exclude ?? [];
|
|
162
|
+
if (!exclude.includes('src/**/*.test.ts')) exclude.push('src/**/*.test.ts');
|
|
163
|
+
return { ...config, exclude };
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* The packages the component and its test import: in the library's own `package.json` when it is
|
|
169
|
+
* a workspace package, which pnpm links from, and at the root otherwise. A version already there
|
|
170
|
+
* is left alone.
|
|
171
|
+
*/
|
|
172
|
+
async function addImports(tree, root) {
|
|
173
|
+
const manifest = joinPathFragments(root, 'package.json');
|
|
174
|
+
const rootManifest = readJson(tree, 'package.json');
|
|
175
|
+
if (tree.exists(manifest)) {
|
|
176
|
+
const own = async (wanted) => asSaved(tree, native.reuseRootRanges(wanted, rootManifest));
|
|
177
|
+
return addDependenciesToPackageJson(
|
|
178
|
+
tree,
|
|
179
|
+
await own(imports.dependencies),
|
|
180
|
+
await own(imports.devDependencies),
|
|
181
|
+
manifest,
|
|
182
|
+
true,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
const existing = Object.keys({ ...rootManifest.dependencies, ...rootManifest.devDependencies });
|
|
186
|
+
return addDependenciesToPackageJson(
|
|
187
|
+
tree,
|
|
188
|
+
await asSaved(tree, imports.dependencies, existing),
|
|
189
|
+
await asSaved(tree, imports.devDependencies, existing),
|
|
190
|
+
'package.json',
|
|
191
|
+
true,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* @param {import('@nx/devkit').Tree} tree
|
|
197
|
+
* @param {{ directory: string, name?: string, tags?: string, skipInstall?: boolean, skipFormat?: boolean }} options
|
|
198
|
+
*/
|
|
199
|
+
async function library(tree, options) {
|
|
200
|
+
const directory = path.normalize(options.directory).replace(/\/$/, '');
|
|
201
|
+
const { dependencies, devDependencies } = readJson(tree, 'package.json');
|
|
202
|
+
const angular = Boolean({ ...dependencies, ...devDependencies }['@nx/angular']);
|
|
203
|
+
const shared = { directory, name: options.name, tags: options.tags, unitTestRunner: 'none' };
|
|
204
|
+
if (angular) await bases.angular(tree, { ...shared, skipFormat: true });
|
|
205
|
+
else await bases.js(tree, { ...shared, bundler: 'none', skipFormat: true });
|
|
206
|
+
|
|
207
|
+
const { name, project } = projectAt(tree, directory);
|
|
208
|
+
const file = names(options.name ?? path.basename(directory)).fileName;
|
|
209
|
+
replaceComponent(tree, project, file);
|
|
210
|
+
|
|
211
|
+
const aliases = hasPathAliases(tree, usesWorkspaces(tree));
|
|
212
|
+
tree.write(joinPathFragments(directory, 'vitest.config.mts'), native.vitestConfig(aliases));
|
|
213
|
+
writeSpecConfig(tree, directory, angular);
|
|
214
|
+
updateProjectConfiguration(tree, name, {
|
|
215
|
+
...project,
|
|
216
|
+
targets: {
|
|
217
|
+
...project.targets,
|
|
218
|
+
test: {
|
|
219
|
+
executor: 'nx:run-commands',
|
|
220
|
+
options: { cwd: directory, command: 'vitest run' },
|
|
221
|
+
cache: true,
|
|
222
|
+
inputs: ['default', '^production'],
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
await addImports(tree, directory);
|
|
228
|
+
// `nxViteTsPaths()`, which the Vitest config uses to reach the workspace's other libraries.
|
|
229
|
+
if (aliases) {
|
|
230
|
+
const vite = { '@nx/vite': workspaceNxVersion(tree) };
|
|
231
|
+
addDependenciesToPackageJson(tree, {}, vite, 'package.json', true);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (!options.skipFormat) await formatFiles(tree);
|
|
235
|
+
if (options.skipInstall) return () => {};
|
|
236
|
+
return () => installPackagesTask(tree, true);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
module.exports = { library, bases, generatorDefaults };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/schema",
|
|
3
|
+
"$id": "NgNativeNxLibrary",
|
|
4
|
+
"title": "Angular Native library",
|
|
5
|
+
"description": "A library of native components, whose tests render on the fake Fabric as an app's do.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"directory": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "Where the library goes, relative to the workspace root: packages/ui.",
|
|
11
|
+
"$default": { "$source": "argv", "index": 0 },
|
|
12
|
+
"x-prompt": "Which directory should the library be created in?"
|
|
13
|
+
},
|
|
14
|
+
"name": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"description": "The project name. Defaults to the directory's last segment.",
|
|
17
|
+
"pattern": "^[a-z][a-z0-9-]*$"
|
|
18
|
+
},
|
|
19
|
+
"tags": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"description": "Comma-separated tags for the project."
|
|
22
|
+
},
|
|
23
|
+
"skipInstall": {
|
|
24
|
+
"type": "boolean",
|
|
25
|
+
"description": "Write the files without installing.",
|
|
26
|
+
"default": false
|
|
27
|
+
},
|
|
28
|
+
"skipFormat": {
|
|
29
|
+
"type": "boolean",
|
|
30
|
+
"description": "Leave the generated files unformatted.",
|
|
31
|
+
"default": false,
|
|
32
|
+
"x-priority": "internal"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"required": ["directory"]
|
|
36
|
+
}
|
package/migrations.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"generators": {
|
|
3
|
+
"sync-app-versions": {
|
|
4
|
+
"version": "0.3.0",
|
|
5
|
+
"description": "Move the @ng-native packages each project's own package.json lists to the new version.",
|
|
6
|
+
"implementation": "./migrations/run.cjs#sync-app-versions"
|
|
7
|
+
},
|
|
8
|
+
"split-store-and-player": {
|
|
9
|
+
"version": "0.3.0",
|
|
10
|
+
"description": "Import Storage, SecureStorage, audioPlayer and videoPlayer from the entry points that now hold them.",
|
|
11
|
+
"implementation": "./migrations/run.cjs#split-store-and-player"
|
|
12
|
+
},
|
|
13
|
+
"tracking-available-getter": {
|
|
14
|
+
"version": "0.3.0",
|
|
15
|
+
"description": "Read Tracking's available as a property rather than calling it.",
|
|
16
|
+
"implementation": "./migrations/run.cjs#tracking-available-getter"
|
|
17
|
+
},
|
|
18
|
+
"device-react-native-import": {
|
|
19
|
+
"version": "0.3.0",
|
|
20
|
+
"description": "Note each import of reactNative or ReactNative from @ng-native/device, which no longer exports them.",
|
|
21
|
+
"implementation": "./migrations/run.cjs#device-react-native-import"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
package/native-app.cjs
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* What an Angular Native app is made of, in an Nx workspace.
|
|
3
3
|
*
|
|
4
4
|
* The source of truth is `template/`, the starter `create-expo-app` uses and the setup this project
|
|
5
|
-
* verifies before every release. `files/` holds verbatim copies of the template's source files
|
|
6
|
-
* the versions
|
|
7
|
-
*
|
|
5
|
+
* verifies before every release. `files/` holds verbatim copies of the template's source files,
|
|
6
|
+
* the versions are the template's own, and `native-app.test.ts` fails the moment either drifts
|
|
7
|
+
* from it.
|
|
8
8
|
*
|
|
9
9
|
* Three files differ from the template's, each because of something Nx does:
|
|
10
10
|
*
|
|
@@ -16,59 +16,35 @@
|
|
|
16
16
|
* - `vitest.config.mts` adds `nxViteTsPaths` when the workspace has `@nx/vite`, for the same
|
|
17
17
|
* aliases, which Vitest does not read from tsconfig either.
|
|
18
18
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
19
|
+
* What does not depend on Nx, the dependencies, `app.json` and bundle identifier among them, is
|
|
20
|
+
* `@ng-native/migrate/native-app.cjs`, which `@ng-native/schematics` builds its apps from too.
|
|
21
21
|
*/
|
|
22
22
|
const { readFileSync } = require('node:fs');
|
|
23
23
|
const path = require('node:path');
|
|
24
24
|
const semver = require('semver');
|
|
25
|
+
const shared = require('@ng-native/migrate/native-app.cjs');
|
|
25
26
|
|
|
26
|
-
const {
|
|
27
|
-
|
|
28
|
-
const FRAMEWORK = [
|
|
29
|
-
'@ng-native/components',
|
|
30
|
-
'@ng-native/device',
|
|
31
|
-
'@ng-native/fabric',
|
|
32
|
-
'@ng-native/metro',
|
|
33
|
-
'@ng-native/platform',
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
/** @type {Record<string, string>} */
|
|
37
|
-
const dependencies = {
|
|
38
|
-
...Object.fromEntries(FRAMEWORK.map((name) => [name, version])),
|
|
39
|
-
'@angular/common': '^22.0.0',
|
|
40
|
-
'@angular/core': '^22.0.0',
|
|
41
|
-
expo: '~57.0.26',
|
|
42
|
-
'expo-status-bar': '~57.0.1',
|
|
43
|
-
'expo-system-ui': '~57.0.4',
|
|
44
|
-
react: '19.2.3',
|
|
45
|
-
'react-native': '0.86.3',
|
|
46
|
-
'react-native-safe-area-context': '~5.7.0',
|
|
47
|
-
};
|
|
27
|
+
const { dependencies, devDependencies, accepted } = shared;
|
|
48
28
|
|
|
49
29
|
/**
|
|
50
30
|
* What `nx add` puts beside `@nx/expo`, in place of the `@nx/expo:init` it does not run: see
|
|
51
|
-
* `init/index.cjs`. `@expo/cli` and `@
|
|
52
|
-
* on.
|
|
31
|
+
* `init/index.cjs`. `@expo/cli`, `@babel/runtime` and `@expo/metro` are the ranges the template's
|
|
32
|
+
* `expo` depends on, and `@babel/core` is the app's own, which every plugin in Expo's Babel preset peers on.
|
|
53
33
|
*/
|
|
54
34
|
const expoCompanions = {
|
|
55
35
|
'react-dom': dependencies.react,
|
|
56
36
|
'@expo/cli': '^57.0.27',
|
|
57
37
|
'@babel/runtime': '^7.20.0',
|
|
38
|
+
'@babel/core': devDependencies['@babel/core'],
|
|
39
|
+
'@expo/metro': '~56.0.2',
|
|
58
40
|
};
|
|
59
41
|
|
|
60
|
-
/**
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
'@babel/core': '^7.29.7',
|
|
67
|
-
'@ng-native/testing': version,
|
|
68
|
-
'@types/react': '~19.2.2',
|
|
69
|
-
typescript: '~6.0.3',
|
|
70
|
-
vitest: '^5.0.0',
|
|
71
|
-
};
|
|
42
|
+
/**
|
|
43
|
+
* The template's `typecheck` script. It loads the Metro config first, which builds the sheet
|
|
44
|
+
* `@ng-native/tailwind` generates into `.angular-native/` once and exits: `src/main.ts` imports it
|
|
45
|
+
* once Tailwind is added, and a fresh checkout has none until Metro has run.
|
|
46
|
+
*/
|
|
47
|
+
const TYPECHECK = 'node metro.config.js && ngc -p tsconfig.json --noEmit';
|
|
72
48
|
|
|
73
49
|
/** The template's files, copied verbatim into the new app. */
|
|
74
50
|
const SOURCE_FILES = ['src/app/app.ts', 'src/main.ts', 'src/app/app.test.ts'];
|
|
@@ -91,74 +67,6 @@ function agentsFile(commands) {
|
|
|
91
67
|
);
|
|
92
68
|
}
|
|
93
69
|
|
|
94
|
-
/** Java's reserved words, which Android refuses as a segment of a package name. */
|
|
95
|
-
const JAVA_KEYWORDS = new Set(
|
|
96
|
-
(
|
|
97
|
-
'abstract assert boolean break byte case catch char class const continue default do double ' +
|
|
98
|
-
'else enum extends false final finally float for goto if implements import instanceof int ' +
|
|
99
|
-
'interface long native new null package private protected public return short static ' +
|
|
100
|
-
'strictfp super switch synchronized this throw throws transient true try void volatile while'
|
|
101
|
-
).split(' '),
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Whether iOS takes `id` as a bundle identifier and Android as a package name: two or more
|
|
106
|
-
* segments, each a letter and then letters or digits (iOS refuses `_`, Android `-`), none of them
|
|
107
|
-
* a Java keyword.
|
|
108
|
-
*
|
|
109
|
-
* @param {string} id
|
|
110
|
-
*/
|
|
111
|
-
function isBundleIdentifier(id) {
|
|
112
|
-
const segments = id.split('.');
|
|
113
|
-
return (
|
|
114
|
-
segments.length > 1 &&
|
|
115
|
-
segments.every((s) => /^[a-z][a-z0-9]*$/i.test(s) && !JAVA_KEYWORDS.has(s))
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* `com.<scope>.<name>`, for an app given no `--bundleIdentifier`, where prebuild would otherwise
|
|
121
|
-
* make it `com.anonymous.<name>`. The scope is left out when it cannot be made a segment.
|
|
122
|
-
*
|
|
123
|
-
* @param {string} name the app's name, without a scope
|
|
124
|
-
* @param {string | undefined} scope the workspace's npm scope, `@acme`
|
|
125
|
-
*/
|
|
126
|
-
function bundleIdentifier(name, scope) {
|
|
127
|
-
const clean = (text) => text.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
128
|
-
const valid = (segment) => /^[a-z]/.test(segment) && !JAVA_KEYWORDS.has(segment);
|
|
129
|
-
const org = clean(scope ?? '');
|
|
130
|
-
const app = clean(name);
|
|
131
|
-
return ['com', ...(valid(org) ? [org] : []), valid(app) ? app : `app${app}`].join('.');
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* The template's `app.json`, named for this app, without the template's icons: they are the
|
|
136
|
-
* template's branding, and Expo draws its own default until `icon` is set.
|
|
137
|
-
*
|
|
138
|
-
* @param {string} name the project name, which is also the Expo slug
|
|
139
|
-
* @param {string} [id] the iOS bundle identifier and Android package, by default one from `name`
|
|
140
|
-
*/
|
|
141
|
-
function appJson(name, id) {
|
|
142
|
-
const slug = name.replace(/^@[^/]+\//, '');
|
|
143
|
-
const bundle = id ?? bundleIdentifier(slug, name.match(/^@[^/]+/)?.[0]);
|
|
144
|
-
const expo = {
|
|
145
|
-
name: slug,
|
|
146
|
-
slug,
|
|
147
|
-
version: '1.0.0',
|
|
148
|
-
// Not left to Expo, which adds `web` whenever `react-dom` resolves, and @nx/react puts one there.
|
|
149
|
-
platforms: ['ios', 'android'],
|
|
150
|
-
orientation: 'portrait',
|
|
151
|
-
userInterfaceStyle: 'dark',
|
|
152
|
-
ios: { supportsTablet: true, bundleIdentifier: bundle },
|
|
153
|
-
android: { package: bundle, predictiveBackGestureEnabled: false },
|
|
154
|
-
scheme: slug.replace(/[^a-z0-9]/gi, '').toLowerCase(),
|
|
155
|
-
// The template's: Expo otherwise guesses a router root from src/app and says so on every start.
|
|
156
|
-
extra: { router: { root: 'src/app' } },
|
|
157
|
-
plugins: ['@ng-native/metro'],
|
|
158
|
-
};
|
|
159
|
-
return JSON.stringify({ expo }, null, 2) + '\n';
|
|
160
|
-
}
|
|
161
|
-
|
|
162
70
|
const METRO_CONFIG = `const { withNxMetro } = require('@nx/expo');
|
|
163
71
|
const { getDefaultConfig } = require('expo/metro-config');
|
|
164
72
|
const { withAngularNative } = require('@ng-native/metro/config.cjs');
|
|
@@ -226,32 +134,6 @@ function vitestConfig(withPaths) {
|
|
|
226
134
|
.replace('plugins: [ngNative()],', 'plugins: [nxViteTsPaths(), ngNative()],');
|
|
227
135
|
}
|
|
228
136
|
|
|
229
|
-
/**
|
|
230
|
-
* The dependencies an integrated workspace's app lists in its own `package.json`, at the ranges
|
|
231
|
-
* the root installs, though nothing installs from it.
|
|
232
|
-
*
|
|
233
|
-
* All of them, because Expo's autolinking links the native modules the app's `package.json` names
|
|
234
|
-
* and no others: without `react-native-safe-area-context` here, Android crashed on RNCSafeAreaView.
|
|
235
|
-
* And `expo prebuild`, which `expo run:ios` starts with, adds `expo`, `react` or `react-native` if
|
|
236
|
-
* it does not find them and then offers to install them into the app's directory, which would put a
|
|
237
|
-
* second copy of React Native beside the root's.
|
|
238
|
-
*
|
|
239
|
-
* @param {{ dependencies?: Record<string, string>, devDependencies?: Record<string, string> }} root
|
|
240
|
-
*/
|
|
241
|
-
function prebuildPins(root) {
|
|
242
|
-
const installed = { ...root.devDependencies, ...root.dependencies };
|
|
243
|
-
return Object.fromEntries(
|
|
244
|
-
Object.keys(dependencies).map((pkg) => [pkg, installed[pkg] ?? dependencies[pkg]]),
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Where what a workspace already has can be older than what a new app is given: `@ng-native/testing`
|
|
250
|
-
* installs Vitest 5 but its plugin runs on 4, which `ng new` 22.1 and the Nx preset still start on.
|
|
251
|
-
* The range here is that package's `vitest` peer, and a test holds the two together.
|
|
252
|
-
*/
|
|
253
|
-
const accepted = { vitest: '^4.0.8 || ^5.0.0' };
|
|
254
|
-
|
|
255
137
|
/**
|
|
256
138
|
* The ranges a workspace package's `package.json` lists: the root's own where everything it can
|
|
257
139
|
* resolve to is a version Angular Native accepts, so that pnpm does not install a second Vitest
|
|
@@ -276,49 +158,15 @@ function reuseRootRanges(wanted, root) {
|
|
|
276
158
|
);
|
|
277
159
|
}
|
|
278
160
|
|
|
279
|
-
/**
|
|
280
|
-
* Why an existing dependency would stop this app installing or running, as a sentence each.
|
|
281
|
-
*
|
|
282
|
-
* Only ranges already in the manifest are judged, and only a range that cannot overlap the one
|
|
283
|
-
* wanted is a conflict: `~6.0.2` and `~6.0.3` resolve to the same TypeScript. A missing package is
|
|
284
|
-
* added at the version above. Nothing is rewritten, because moving a workspace's Angular or Vitest
|
|
285
|
-
* is an upgrade of every project in it, and that belongs to `nx migrate`, not to a generator.
|
|
286
|
-
*
|
|
287
|
-
* @param {{ dependencies?: Record<string, string>, devDependencies?: Record<string, string> }} manifest
|
|
288
|
-
* @returns {string[]}
|
|
289
|
-
*/
|
|
290
|
-
function conflicts(manifest) {
|
|
291
|
-
const existing = { ...manifest.dependencies, ...manifest.devDependencies };
|
|
292
|
-
const problems = [];
|
|
293
|
-
for (const [name, installed] of Object.entries({ ...dependencies, ...devDependencies })) {
|
|
294
|
-
const range = existing[name];
|
|
295
|
-
const wanted = accepted[name] ?? installed;
|
|
296
|
-
if (!range || name.startsWith('@ng-native/')) continue;
|
|
297
|
-
if (semver.validRange(range) && !semver.intersects(range, wanted)) {
|
|
298
|
-
problems.push(
|
|
299
|
-
`${name} is ${range} here, and Angular Native needs ${wanted}. ` +
|
|
300
|
-
`npm will refuse the install until it is moved.`,
|
|
301
|
-
);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
return problems;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
161
|
module.exports = {
|
|
308
|
-
|
|
309
|
-
devDependencies,
|
|
162
|
+
...shared,
|
|
310
163
|
SOURCE_FILES,
|
|
311
164
|
sourceFile,
|
|
312
165
|
agentsFile,
|
|
313
|
-
appJson,
|
|
314
|
-
bundleIdentifier,
|
|
315
|
-
isBundleIdentifier,
|
|
316
166
|
METRO_CONFIG,
|
|
167
|
+
TYPECHECK,
|
|
317
168
|
tsconfig,
|
|
318
169
|
vitestConfig,
|
|
319
|
-
prebuildPins,
|
|
320
170
|
reuseRootRanges,
|
|
321
171
|
expoCompanions,
|
|
322
|
-
conflicts,
|
|
323
|
-
accepted,
|
|
324
172
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ng-native/nx",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Nx generators for Angular Native: nx add,
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Nx generators for Angular Native: nx add, an Expo app project whose targets run it, and native libraries and components.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"angular",
|
|
7
7
|
"react-native",
|
|
@@ -17,7 +17,27 @@
|
|
|
17
17
|
],
|
|
18
18
|
"type": "module",
|
|
19
19
|
"generators": "./generators.json",
|
|
20
|
+
"nx-migrations": {
|
|
21
|
+
"migrations": "./migrations.json",
|
|
22
|
+
"packageGroup": [
|
|
23
|
+
"@ng-native/components",
|
|
24
|
+
"@ng-native/device",
|
|
25
|
+
"@ng-native/expo",
|
|
26
|
+
"@ng-native/fabric",
|
|
27
|
+
"@ng-native/icons",
|
|
28
|
+
"@ng-native/metro",
|
|
29
|
+
"@ng-native/migrate",
|
|
30
|
+
"@ng-native/nx",
|
|
31
|
+
"@ng-native/platform",
|
|
32
|
+
"@ng-native/router",
|
|
33
|
+
"@ng-native/schematics",
|
|
34
|
+
"@ng-native/tailwind",
|
|
35
|
+
"@ng-native/testing",
|
|
36
|
+
"@ng-native/web"
|
|
37
|
+
]
|
|
38
|
+
},
|
|
20
39
|
"dependencies": {
|
|
40
|
+
"@ng-native/migrate": "0.3.0",
|
|
21
41
|
"@nx/devkit": "^23.0.0",
|
|
22
42
|
"minimatch": "^10.0.0",
|
|
23
43
|
"semver": "^7.7.0"
|
|
@@ -26,9 +46,11 @@
|
|
|
26
46
|
"nx": "^23.0.0"
|
|
27
47
|
},
|
|
28
48
|
"devDependencies": {
|
|
29
|
-
"@types/semver": "^7.7.0"
|
|
49
|
+
"@types/semver": "^7.7.0",
|
|
50
|
+
"typescript": "~6.0.3"
|
|
30
51
|
},
|
|
31
52
|
"nx": {
|
|
53
|
+
"projectType": "library",
|
|
32
54
|
"tags": [
|
|
33
55
|
"scope:nx",
|
|
34
56
|
"layer:build"
|
|
@@ -47,6 +69,7 @@
|
|
|
47
69
|
"files": [
|
|
48
70
|
"*.cjs",
|
|
49
71
|
"generators.json",
|
|
72
|
+
"migrations.json",
|
|
50
73
|
"*/*.cjs",
|
|
51
74
|
"*/*.json",
|
|
52
75
|
"files"
|
|
@@ -56,6 +79,6 @@
|
|
|
56
79
|
},
|
|
57
80
|
"scripts": {
|
|
58
81
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
59
|
-
"test": "node --test \"*.test.ts\" \"{init,application}/*.test.ts\""
|
|
82
|
+
"test": "node --test \"*.test.ts\" \"{init,application,sync,library,component,migrations,tailwind}/*.test.ts\""
|
|
60
83
|
}
|
|
61
84
|
}
|