@flarum/cli 3.0.12 → 3.0.14
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 +33 -1
- package/boilerplate/skeleton/extension/composer.json +4 -0
- package/boilerplate/skeleton/extension/js/package.json +5 -5
- package/boilerplate/skeleton/extension/js/tsconfig.json +3 -0
- package/boilerplate/skeleton/extension/js/tsconfig.test.json +1 -0
- package/boilerplate/stubs/backend/advanced-api-resource.php +3 -3
- package/boilerplate/stubs/backend/model-factory.php +17 -0
- package/boilerplate/stubs/backend/model.php +3 -0
- package/lib/base-command.d.ts +21 -3
- package/lib/base-command.js +52 -5
- package/lib/commands/make/backend/model.js +10 -0
- package/lib/commands/upgrade/2.0.d.ts +43 -0
- package/lib/commands/upgrade/2.0.js +128 -2
- package/lib/steps/gen-ext-scaffolder.d.ts +5 -0
- package/lib/steps/gen-ext-scaffolder.js +17 -2
- package/lib/steps/stubs/backend/advanced-api-resource.js +1 -1
- package/lib/steps/stubs/backend/model-factory.d.ts +24 -0
- package/lib/steps/stubs/backend/model-factory.js +33 -0
- package/lib/steps/upgrade/twopointoh/backend/email-views.js +22 -30
- package/lib/steps/upgrade/twopointoh/backend/extension-models.d.ts +30 -0
- package/lib/steps/upgrade/twopointoh/backend/extension-models.js +34 -0
- package/lib/steps/upgrade/twopointoh/backend/json-api.d.ts +6 -0
- package/lib/steps/upgrade/twopointoh/backend/json-api.js +14 -0
- package/lib/steps/upgrade/twopointoh/backend/misc.d.ts +6 -0
- package/lib/steps/upgrade/twopointoh/backend/misc.js +19 -7
- package/lib/steps/upgrade/twopointoh/backend/model-factories.d.ts +18 -0
- package/lib/steps/upgrade/twopointoh/backend/model-factories.js +73 -0
- package/lib/steps/upgrade/twopointoh/backend/phpunit.d.ts +2 -0
- package/lib/steps/upgrade/twopointoh/backend/phpunit.js +16 -8
- package/lib/steps/upgrade/twopointoh/backend/search.d.ts +9 -0
- package/lib/steps/upgrade/twopointoh/backend/search.js +28 -10
- package/lib/steps/upgrade/twopointoh/base.d.ts +38 -0
- package/lib/steps/upgrade/twopointoh/base.js +73 -12
- package/lib/steps/upgrade/twopointoh/dependencies.d.ts +55 -0
- package/lib/steps/upgrade/twopointoh/dependencies.js +317 -20
- package/lib/steps/upgrade/twopointoh/frontend/admin-extender.d.ts +86 -0
- package/lib/steps/upgrade/twopointoh/frontend/admin-extender.js +842 -0
- package/lib/steps/upgrade/twopointoh/frontend/code-splitting/usage-of-lazy-modules.js +110 -18
- package/lib/steps/upgrade/twopointoh/frontend/export-registry/import-ext.js +13 -16
- package/lib/steps/upgrade/twopointoh/frontend/misc.d.ts +23 -0
- package/lib/steps/upgrade/twopointoh/frontend/misc.js +147 -8
- package/lib/steps/upgrade/twopointoh/infra.d.ts +16 -1
- package/lib/steps/upgrade/twopointoh/infra.js +87 -5
- package/lib/steps/upgrade/twopointoh/step-failures.d.ts +8 -0
- package/lib/steps/upgrade/twopointoh/step-failures.js +6 -0
- package/lib/steps/upgrade/twopointoh/todos.d.ts +22 -0
- package/lib/steps/upgrade/twopointoh/todos.js +57 -0
- package/lib/steps/upgrade/twopointoh/typescript-5-9.d.ts +15 -0
- package/lib/steps/upgrade/twopointoh/typescript-5-9.js +250 -0
- package/lib/utils/ast.js +3 -2
- package/lib/utils/babel-ts.d.ts +9 -0
- package/lib/utils/babel-ts.js +18 -0
- package/lib/utils/composer.d.ts +5 -0
- package/lib/utils/composer.js +10 -1
- package/lib/utils/terminal.d.ts +13 -0
- package/lib/utils/terminal.js +26 -0
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/php-subsystem/index.php +12 -0
- package/php-subsystem/src/NodeUtil.php +19 -9
- package/php-subsystem/src/NodeVisitors/AddExtender.php +49 -1
- package/php-subsystem/src/Upgrade/TwoPointOh/JsonApi.php +2 -20
- package/php-subsystem/src/Upgrade/TwoPointOh/Misc.php +13 -17
- package/php-subsystem/src/Upgrade/TwoPointOh/ModelFactories.php +943 -0
- package/php-subsystem/src/Upgrade/TwoPointOh/PhpUnit.php +476 -71
- package/php-subsystem/src/Upgrade/TwoPointOh/Search.php +97 -13
|
@@ -3,10 +3,101 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
4
|
const base_1 = require("./base");
|
|
5
5
|
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
6
|
+
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
7
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
8
|
+
const typescript_5_9_1 = require("./typescript-5-9");
|
|
9
|
+
// What `fl init` gave 1.x extensions. `sed -i` with no suffix is GNU sed's
|
|
10
|
+
// form: BSD sed, as on macOS, takes the next argument as the suffix and fails.
|
|
11
|
+
// With the suffix attached, both work.
|
|
12
|
+
const GNU_POST_BUILD_TYPINGS = "find dist-typings -type f -name '*.d.ts' -print0 | xargs -0 sed -i 's,../src/@types,@types,g'";
|
|
13
|
+
const PORTABLE_POST_BUILD_TYPINGS = "find dist-typings -type f -name '*.d.ts' -print0 | xargs -0 sed -i.bak 's,../src/@types,@types,g' && find dist-typings -type f -name '*.d.ts.bak' -delete";
|
|
14
|
+
// Packages an extension only needs to build, check and test its frontend.
|
|
15
|
+
const BUILD_TOOLING = new Set([
|
|
16
|
+
'@flarum/jest-config',
|
|
17
|
+
'@flarum/prettier-config',
|
|
18
|
+
'flarum-tsconfig',
|
|
19
|
+
'flarum-webpack-config',
|
|
20
|
+
'prettier',
|
|
21
|
+
'typescript',
|
|
22
|
+
'typescript-coverage-report',
|
|
23
|
+
'webpack',
|
|
24
|
+
'webpack-cli',
|
|
25
|
+
]);
|
|
26
|
+
// The scripts that build and check an extension's typings, as the reusable
|
|
27
|
+
// frontend workflow runs them.
|
|
28
|
+
const TYPINGS_SCRIPTS = ['clean-typings', 'build-typings', 'post-build-typings', 'check-typings', 'check-typings-coverage'];
|
|
29
|
+
// What those scripts run.
|
|
30
|
+
const TYPINGS_TOOLING = ['typescript', 'typescript-coverage-report'];
|
|
31
|
+
/** The package.json `fl init` gives a new extension, for the given package manager. */
|
|
32
|
+
function scaffoldedPackageJson(packageManager) {
|
|
33
|
+
const skeleton = fs_1.default.readFileSync(path_1.default.resolve(__dirname, '../../../../boilerplate/skeleton/extension/js/package.json'), 'utf8');
|
|
34
|
+
// EJS placeholders only appear inside strings, so it parses as JSON.
|
|
35
|
+
return JSON.parse(skeleton.replace(/<%= params\.jsPackageManager %>/g, packageManager));
|
|
36
|
+
}
|
|
37
|
+
const TYPES_COMMENT = [
|
|
38
|
+
'Only load the type packages the code imports, not every one in',
|
|
39
|
+
'node_modules, such as the @types/node webpack brings along.',
|
|
40
|
+
];
|
|
41
|
+
/**
|
|
42
|
+
* The tsconfig.json with an option first in its compilerOptions, and the
|
|
43
|
+
* comment lines above it, indented as the options there are. Null when it has
|
|
44
|
+
* no compilerOptions to put it in.
|
|
45
|
+
*/
|
|
46
|
+
function insertCompilerOption(code, comment, option) {
|
|
47
|
+
var _a;
|
|
48
|
+
const open = code.match(/"compilerOptions"\s*:\s*{/);
|
|
49
|
+
if (!open)
|
|
50
|
+
return null;
|
|
51
|
+
const at = open.index + open[0].length;
|
|
52
|
+
const eol = code.includes('\r\n') ? '\r\n' : '\n';
|
|
53
|
+
const next = code.slice(at).match(/\r?\n([\t ]*)\S/);
|
|
54
|
+
const indent = (_a = next === null || next === void 0 ? void 0 : next[1]) !== null && _a !== void 0 ? _a : ' ';
|
|
55
|
+
const lines = [...(comment !== null && comment !== void 0 ? comment : []).map((line) => `// ${line}`), `${option},`];
|
|
56
|
+
return code.slice(0, at) + lines.map((line) => eol + indent + line).join('') + code.slice(at);
|
|
57
|
+
}
|
|
58
|
+
/** Dependencies in the alphabetical order package managers keep them in. */
|
|
59
|
+
const sortedByName = (dependencies) => Object.fromEntries(Object.entries(dependencies).sort(([a], [b]) => a.localeCompare(b)));
|
|
60
|
+
/**
|
|
61
|
+
* Whether a TypeScript constraint could resolve to TypeScript 6 or newer:
|
|
62
|
+
* `^6.0.3`, `>=4.2`, `latest`, `*`. A caret, tilde or exact constraint on 4 or
|
|
63
|
+
* 5 can't.
|
|
64
|
+
*/
|
|
65
|
+
const allowsSixOrNewer = (constraint) => {
|
|
66
|
+
const match = constraint.trim().match(/^[=^~]?\s*v?(\d+)(?:\.|$)/);
|
|
67
|
+
return !match || Number(match[1]) >= 6;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* The lowest version a simple constraint allows, as [major, minor, patch]:
|
|
71
|
+
* `^5.1.4`, `~4.7`, `>=8.3`, `5.x`. Null for anything else, such as `*`, a
|
|
72
|
+
* range with `||` or a prerelease.
|
|
73
|
+
*/
|
|
74
|
+
const lowestVersion = (constraint) => {
|
|
75
|
+
const match = constraint.trim().match(/^(?:\^|~|>=|=)?\s*v?(\d+)(?:\.(\d+|x|\*))?(?:\.(\d+|x|\*))?$/);
|
|
76
|
+
return match ? match.slice(1).map((part) => (part && /^\d+$/.test(part) ? Number(part) : 0)) : null;
|
|
77
|
+
};
|
|
78
|
+
/** Whether a constraint asks for a newer version than another does. */
|
|
79
|
+
const asksForNewer = (constraint, than) => {
|
|
80
|
+
const asked = lowestVersion(constraint);
|
|
81
|
+
const other = lowestVersion(than);
|
|
82
|
+
if (!asked || !other)
|
|
83
|
+
return false;
|
|
84
|
+
const differs = asked.findIndex((part, i) => part !== other[i]);
|
|
85
|
+
return differs !== -1 && asked[differs] > other[differs];
|
|
86
|
+
};
|
|
6
87
|
class Dependencies extends base_1.BaseUpgradeStep {
|
|
7
88
|
constructor() {
|
|
8
89
|
super(...arguments);
|
|
9
90
|
this.type = 'Update the extension dependencies to the latest versions.';
|
|
91
|
+
/** The TypeScript the extension asked for, when it allowed TypeScript 6 or newer. */
|
|
92
|
+
this.typeScriptWas = null;
|
|
93
|
+
/** What was removed from tsconfig.json, as TypeScript 5.9 refuses it. */
|
|
94
|
+
this.removedCompilerOptions = [];
|
|
95
|
+
/** Constraints left as they were, as they already ask for newer versions. */
|
|
96
|
+
this.keptNewer = [];
|
|
97
|
+
/** Tooling moved from dependencies to devDependencies. */
|
|
98
|
+
this.movedToDev = [];
|
|
99
|
+
/** The typings scripts, and the tooling they run, added to package.json. */
|
|
100
|
+
this.addedTypings = [];
|
|
10
101
|
}
|
|
11
102
|
replacements(file) {
|
|
12
103
|
const replacements = [];
|
|
@@ -16,11 +107,18 @@ class Dependencies extends base_1.BaseUpgradeStep {
|
|
|
16
107
|
break;
|
|
17
108
|
case 'js/package.json':
|
|
18
109
|
replacements.push(this.updatePackageJson());
|
|
110
|
+
break;
|
|
111
|
+
case 'js/tsconfig.json':
|
|
112
|
+
replacements.push(this.updateTsConfig(), this.limitTypes());
|
|
113
|
+
break;
|
|
114
|
+
case 'js/tsconfig.test.json':
|
|
115
|
+
replacements.push(this.testTypes());
|
|
116
|
+
break;
|
|
19
117
|
}
|
|
20
118
|
return replacements;
|
|
21
119
|
}
|
|
22
120
|
targets() {
|
|
23
|
-
return ['composer.json', 'js/package.json'];
|
|
121
|
+
return ['composer.json', 'js/package.json', 'js/tsconfig.json', 'js/tsconfig.test.json'];
|
|
24
122
|
}
|
|
25
123
|
gitCommit() {
|
|
26
124
|
return {
|
|
@@ -33,9 +131,33 @@ class Dependencies extends base_1.BaseUpgradeStep {
|
|
|
33
131
|
const composerUpdate = chalk_1.default.bgYellowBright('composer update');
|
|
34
132
|
const yarn = chalk_1.default.bgYellowBright('yarn');
|
|
35
133
|
const npm = chalk_1.default.bgYellowBright('npm install');
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
134
|
+
const notes = [
|
|
135
|
+
this.typeScriptWas &&
|
|
136
|
+
`TypeScript ${this.typeScriptWas} was brought down to ^5.9.3: flarum-tsconfig resolves modules the way TypeScript 6 refuses without ignoreDeprecations.`,
|
|
137
|
+
this.removedCompilerOptions.length > 0 &&
|
|
138
|
+
`Removed from js/tsconfig.json, as TypeScript 5.9 refuses them: ${this.removedCompilerOptions.join(', ')}.`,
|
|
139
|
+
this.addedTypings.length > 0 && `Added what the reusable frontend workflow runs to build and check typings: ${this.addedTypings.join(', ')}.`,
|
|
140
|
+
this.movedToDev.length > 0 && `Moved to devDependencies, as the extension only builds with them: ${this.movedToDev.join(', ')}.`,
|
|
141
|
+
this.keptNewer.length > 0 && `Kept, as they already ask for newer versions than Flarum 2.0 needs: ${this.keptNewer.join(', ')}.`,
|
|
142
|
+
].filter(Boolean);
|
|
143
|
+
return [
|
|
144
|
+
'Dependencies updated. Please review the changes and update any other dependency versions as needed.',
|
|
145
|
+
...notes,
|
|
146
|
+
`It is recommended to run ${composerInstall} or ${composerUpdate} and ${yarn} or ${npm} now.`,
|
|
147
|
+
'But you may also leave that until the upgrade process is over.',
|
|
148
|
+
].join('\n ');
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Require at least the given version, leaving a constraint that already
|
|
152
|
+
* asks for a newer one as it is.
|
|
153
|
+
*/
|
|
154
|
+
raise(dependencies, name, version) {
|
|
155
|
+
if (asksForNewer(String(dependencies[name]), version)) {
|
|
156
|
+
this.keptNewer.push(`${name} ${dependencies[name]}`);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
dependencies[name] = version;
|
|
160
|
+
}
|
|
39
161
|
}
|
|
40
162
|
updateComposerJson() {
|
|
41
163
|
return (_file, _code, advanced) => {
|
|
@@ -52,7 +174,7 @@ class Dependencies extends base_1.BaseUpgradeStep {
|
|
|
52
174
|
}
|
|
53
175
|
});
|
|
54
176
|
if (key === 'flarum/core') {
|
|
55
|
-
composer.require
|
|
177
|
+
this.raise(composer.require, key, '^2.0.0');
|
|
56
178
|
}
|
|
57
179
|
else if (key.startsWith('flarum/') && value !== '*') {
|
|
58
180
|
composer.require[key] = '*';
|
|
@@ -67,22 +189,20 @@ class Dependencies extends base_1.BaseUpgradeStep {
|
|
|
67
189
|
}
|
|
68
190
|
});
|
|
69
191
|
if (['flarum/core', 'flarum/testing', 'flarum/phpstan'].includes(key)) {
|
|
70
|
-
composer['require-dev']
|
|
192
|
+
this.raise(composer['require-dev'], key, '^2.0.0');
|
|
71
193
|
}
|
|
72
194
|
else if (key.startsWith('flarum/') && value.startsWith('^1')) {
|
|
73
195
|
composer['require-dev'][key] = '*';
|
|
74
196
|
}
|
|
75
197
|
}
|
|
198
|
+
// An extension using newer PHP than Flarum 2.0 needs keeps asking for it.
|
|
76
199
|
if (composer.require.php) {
|
|
77
|
-
composer.require
|
|
200
|
+
this.raise(composer.require, 'php', '^8.3');
|
|
78
201
|
}
|
|
79
|
-
// 2.0
|
|
80
|
-
// extension
|
|
81
|
-
// exists, so only Flarum itself comes from the beta channel.
|
|
82
|
-
composer['minimum-stability'] = 'beta';
|
|
83
|
-
composer['prefer-stable'] = true;
|
|
202
|
+
// Flarum 2.0 is stable, so its constraints resolve without changing the
|
|
203
|
+
// extension's minimum stability.
|
|
84
204
|
if (composer.require['fof/extend']) {
|
|
85
|
-
composer.require
|
|
205
|
+
this.raise(composer.require, 'fof/extend', '^2.0.0');
|
|
86
206
|
}
|
|
87
207
|
return {
|
|
88
208
|
updated: composer,
|
|
@@ -91,20 +211,47 @@ class Dependencies extends base_1.BaseUpgradeStep {
|
|
|
91
211
|
}
|
|
92
212
|
updatePackageJson() {
|
|
93
213
|
return (_file, _code, advanced) => {
|
|
214
|
+
var _a;
|
|
94
215
|
advanced = advanced;
|
|
216
|
+
// The versions a new extension is scaffolded with. 3.1.0 is the first
|
|
217
|
+
// flarum-webpack-config that supplies TypeScript 5.9 and only asks for
|
|
218
|
+
// webpack as a peer, and @flarum/jest-config 2.1.0 tests with the mithril
|
|
219
|
+
// Flarum ships.
|
|
95
220
|
const map = {
|
|
96
|
-
'flarum-webpack-config': '^3.
|
|
221
|
+
'flarum-webpack-config': '^3.1.0',
|
|
97
222
|
'flarum-tsconfig': '^2.0.0',
|
|
98
|
-
'@flarum/jest-config': '^2.
|
|
99
|
-
webpack: '^5.
|
|
223
|
+
'@flarum/jest-config': '^2.1.0',
|
|
224
|
+
webpack: '^5.104.1',
|
|
100
225
|
'webpack-cli': '^4.9.1',
|
|
226
|
+
// What Flarum builds and checks its typings with.
|
|
227
|
+
typescript: '^5.9.3',
|
|
101
228
|
};
|
|
102
|
-
|
|
103
|
-
for (const
|
|
104
|
-
|
|
105
|
-
|
|
229
|
+
// Extensions list the tooling under either key, often split between both.
|
|
230
|
+
for (const dependencies of [advanced.dependencies, advanced.devDependencies]) {
|
|
231
|
+
for (const key of Object.keys(dependencies || {})) {
|
|
232
|
+
// TypeScript is the exception: it moves to what Flarum checks typings
|
|
233
|
+
// with, whatever the extension asked for. flarum-tsconfig resolves
|
|
234
|
+
// modules the way TypeScript 6 refuses, so a newer one comes down,
|
|
235
|
+
// unless the extension's tsconfig already resolves them another way.
|
|
236
|
+
if (key === 'typescript' && allowsSixOrNewer(String(dependencies[key])) && this.tsConfigReadsWithSixOrNewer()) {
|
|
237
|
+
this.keptNewer.push(`${key} ${dependencies[key]}`);
|
|
238
|
+
}
|
|
239
|
+
else if (key === 'typescript') {
|
|
240
|
+
if (allowsSixOrNewer(String(dependencies[key]))) {
|
|
241
|
+
this.typeScriptWas = dependencies[key];
|
|
242
|
+
}
|
|
243
|
+
dependencies[key] = map[key];
|
|
244
|
+
}
|
|
245
|
+
else if (map[key]) {
|
|
246
|
+
this.raise(dependencies, key, map[key]);
|
|
247
|
+
}
|
|
106
248
|
}
|
|
107
249
|
}
|
|
250
|
+
if (((_a = advanced.scripts) === null || _a === void 0 ? void 0 : _a['post-build-typings']) === GNU_POST_BUILD_TYPINGS) {
|
|
251
|
+
advanced.scripts['post-build-typings'] = PORTABLE_POST_BUILD_TYPINGS;
|
|
252
|
+
}
|
|
253
|
+
this.addTypingsScripts(advanced);
|
|
254
|
+
this.moveToolingToDevDependencies(advanced);
|
|
108
255
|
// Remove duplicates from devDependencies
|
|
109
256
|
if (advanced.dependencies && advanced.devDependencies) {
|
|
110
257
|
for (const [key] of Object.entries(advanced.dependencies)) {
|
|
@@ -118,5 +265,155 @@ class Dependencies extends base_1.BaseUpgradeStep {
|
|
|
118
265
|
};
|
|
119
266
|
};
|
|
120
267
|
}
|
|
268
|
+
/**
|
|
269
|
+
* Add the scripts that build and check typings, which a new extension is
|
|
270
|
+
* scaffolded with and the reusable frontend workflow runs, along with the
|
|
271
|
+
* tooling they need. Scripts the extension already has are left alone.
|
|
272
|
+
*/
|
|
273
|
+
addTypingsScripts(pkg) {
|
|
274
|
+
const missing = TYPINGS_SCRIPTS.filter((name) => { var _a; return !((_a = pkg.scripts) === null || _a === void 0 ? void 0 : _a[name]); });
|
|
275
|
+
if (missing.length === 0)
|
|
276
|
+
return;
|
|
277
|
+
if (!this.hasTsConfig()) {
|
|
278
|
+
this.leaveTodo('js/tsconfig.json', 'Add a tsconfig.json, then the scripts that build and check typings, which the reusable frontend workflow runs: ' + missing.join(', ') + '.');
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const scaffolded = scaffoldedPackageJson(this.packageManager());
|
|
282
|
+
pkg.scripts = Object.assign({}, pkg.scripts);
|
|
283
|
+
for (const name of missing) {
|
|
284
|
+
pkg.scripts[name] = scaffolded.scripts[name];
|
|
285
|
+
}
|
|
286
|
+
const listed = Object.assign(Object.assign({}, pkg.dependencies), pkg.devDependencies);
|
|
287
|
+
const tooling = TYPINGS_TOOLING.filter((name) => !listed[name]);
|
|
288
|
+
if (tooling.length > 0) {
|
|
289
|
+
pkg.devDependencies = sortedByName(Object.assign(Object.assign({}, pkg.devDependencies), Object.fromEntries(tooling.map((name) => [name, scaffolded.devDependencies[name]]))));
|
|
290
|
+
}
|
|
291
|
+
this.addedTypings.push(...missing, ...tooling);
|
|
292
|
+
}
|
|
293
|
+
hasTsConfig() {
|
|
294
|
+
return fs_1.default.existsSync(path_1.default.join(this.root, 'js/tsconfig.json'));
|
|
295
|
+
}
|
|
296
|
+
/** The package manager the extension's frontend uses, by its lockfile. */
|
|
297
|
+
packageManager() {
|
|
298
|
+
return fs_1.default.existsSync(path_1.default.join(this.root, 'js/yarn.lock')) ? 'yarn' : 'npm';
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Flarum 2.0 extensions only build with their tooling: none of it ends up
|
|
302
|
+
* in the bundle, so it belongs in devDependencies. Where the extension lists
|
|
303
|
+
* a tool under both keys, the newer constraint is kept. Other dependencies
|
|
304
|
+
* may be libraries the bundle includes, so they stay where they are.
|
|
305
|
+
*/
|
|
306
|
+
moveToolingToDevDependencies(pkg) {
|
|
307
|
+
const moving = Object.keys(pkg.dependencies || {}).filter((name) => BUILD_TOOLING.has(name) || name.startsWith('@types/'));
|
|
308
|
+
if (moving.length === 0)
|
|
309
|
+
return;
|
|
310
|
+
const devDependencies = Object.assign({}, pkg.devDependencies);
|
|
311
|
+
for (const name of moving) {
|
|
312
|
+
if (!devDependencies[name] || asksForNewer(String(pkg.dependencies[name]), String(devDependencies[name]))) {
|
|
313
|
+
devDependencies[name] = pkg.dependencies[name];
|
|
314
|
+
}
|
|
315
|
+
delete pkg.dependencies[name];
|
|
316
|
+
}
|
|
317
|
+
pkg.devDependencies = sortedByName(devDependencies);
|
|
318
|
+
this.movedToDev.push(...moving);
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Drop the compiler options, and option values, TypeScript 5.9 refuses: it
|
|
322
|
+
* won't read a tsconfig.json with any. A tsconfig.json it can read is left
|
|
323
|
+
* exactly as it was.
|
|
324
|
+
*/
|
|
325
|
+
updateTsConfig() {
|
|
326
|
+
return (_file, _code, advanced) => {
|
|
327
|
+
const tsconfig = advanced;
|
|
328
|
+
const options = tsconfig.compilerOptions || {};
|
|
329
|
+
const removed = [];
|
|
330
|
+
for (const [name, value] of Object.entries(options)) {
|
|
331
|
+
const known = Object.prototype.hasOwnProperty.call(typescript_5_9_1.compilerOptions, name) ? typescript_5_9_1.compilerOptions[name] : null;
|
|
332
|
+
if (!known) {
|
|
333
|
+
removed.push(name);
|
|
334
|
+
delete options[name];
|
|
335
|
+
}
|
|
336
|
+
else if (name === 'lib' && Array.isArray(value)) {
|
|
337
|
+
const kept = value.filter((lib) => typeof lib === 'string' && typescript_5_9_1.libs.includes(lib.toLowerCase()));
|
|
338
|
+
for (const lib of value.filter((lib) => !kept.includes(lib))) {
|
|
339
|
+
removed.push(`lib "${lib}"`);
|
|
340
|
+
}
|
|
341
|
+
if (kept.length === 0) {
|
|
342
|
+
delete options.lib;
|
|
343
|
+
}
|
|
344
|
+
else if (kept.length < value.length) {
|
|
345
|
+
options.lib = kept;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
else if (Array.isArray(known) && typeof value === 'string' && !known.includes(value.toLowerCase())) {
|
|
349
|
+
removed.push(`${name} "${value}"`);
|
|
350
|
+
delete options[name];
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
this.removedCompilerOptions.push(...removed);
|
|
354
|
+
return removed.length > 0 ? { updated: tsconfig } : null;
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* TypeScript loads every type package in node_modules unless told which,
|
|
359
|
+
* including the @types/node that webpack's minimizer brings along. Load only
|
|
360
|
+
* the ones the code imports. The file is edited as text so its comments stay.
|
|
361
|
+
*/
|
|
362
|
+
limitTypes() {
|
|
363
|
+
return (_file, code, advanced) => {
|
|
364
|
+
var _a;
|
|
365
|
+
if (((_a = advanced === null || advanced === void 0 ? void 0 : advanced.compilerOptions) === null || _a === void 0 ? void 0 : _a.types) !== undefined)
|
|
366
|
+
return null;
|
|
367
|
+
const updated = insertCompilerOption(code, TYPES_COMMENT, '"types": []');
|
|
368
|
+
if (!updated)
|
|
369
|
+
return null;
|
|
370
|
+
const globals = Object.keys(Object.assign(Object.assign({}, this.packageJson().dependencies), this.packageJson().devDependencies)).filter((name) => name.startsWith('@types/') && name !== '@types/jest');
|
|
371
|
+
if (globals.length > 0) {
|
|
372
|
+
this.leaveTodo('js/tsconfig.json', `It only loads the type packages the code imports now. If the code uses the globals of ${globals.join(', ')}, list them in compilerOptions.types.`);
|
|
373
|
+
}
|
|
374
|
+
return { updated };
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* A test config extending tsconfig.json would no longer load Jest's globals.
|
|
379
|
+
*/
|
|
380
|
+
testTypes() {
|
|
381
|
+
return (_file, code, advanced) => {
|
|
382
|
+
var _a;
|
|
383
|
+
if (((_a = advanced === null || advanced === void 0 ? void 0 : advanced.compilerOptions) === null || _a === void 0 ? void 0 : _a.types) !== undefined)
|
|
384
|
+
return null;
|
|
385
|
+
const pkg = this.packageJson();
|
|
386
|
+
const listed = Object.assign(Object.assign({}, pkg.dependencies), pkg.devDependencies);
|
|
387
|
+
if (!['@flarum/jest-config', 'jest', '@types/jest'].some((name) => listed[name]))
|
|
388
|
+
return null;
|
|
389
|
+
const updated = insertCompilerOption(code, null, '"types": ["jest"]');
|
|
390
|
+
return updated ? { updated } : null;
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Whether TypeScript 6 and newer accept the extension's tsconfig.json: it
|
|
395
|
+
* replaces the moduleResolution flarum-tsconfig sets, which they refuse, and
|
|
396
|
+
* sets no baseUrl, which they refuse too.
|
|
397
|
+
*/
|
|
398
|
+
tsConfigReadsWithSixOrNewer() {
|
|
399
|
+
var _a, _b;
|
|
400
|
+
try {
|
|
401
|
+
const code = fs_1.default.readFileSync(path_1.default.join(this.root, 'js/tsconfig.json'), 'utf8');
|
|
402
|
+
const options = (_b = (_a = this.advancedContent('js/tsconfig.json', code)) === null || _a === void 0 ? void 0 : _a.compilerOptions) !== null && _b !== void 0 ? _b : {};
|
|
403
|
+
return ['bundler', 'node16', 'nodenext'].includes(String(options.moduleResolution).toLowerCase()) && options.baseUrl === undefined;
|
|
404
|
+
}
|
|
405
|
+
catch (_c) {
|
|
406
|
+
return false;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
/** The extension's js/package.json, or nothing when it has none. */
|
|
410
|
+
packageJson() {
|
|
411
|
+
try {
|
|
412
|
+
return JSON.parse(fs_1.default.readFileSync(path_1.default.join(this.root, 'js/package.json'), 'utf8'));
|
|
413
|
+
}
|
|
414
|
+
catch (_a) {
|
|
415
|
+
return {};
|
|
416
|
+
}
|
|
417
|
+
}
|
|
121
418
|
}
|
|
122
419
|
exports.default = Dependencies;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Binding, NodePath } from '@babel/traverse';
|
|
2
|
+
import * as t from '@babel/types';
|
|
3
|
+
import { BaseUpgradeStep, GitCommit, Replacement } from '../base';
|
|
4
|
+
/** A registration chain written directly in an admin initializer. */
|
|
5
|
+
type Registration = {
|
|
6
|
+
statement: NodePath<t.ExpressionStatement>;
|
|
7
|
+
calls: t.CallExpression[];
|
|
8
|
+
/** What for() names, when it is a string the upgrade can read. */
|
|
9
|
+
context: string | null;
|
|
10
|
+
/** Why it can't move, if it can't. */
|
|
11
|
+
reason: string | null;
|
|
12
|
+
imports: Map<string, Binding>;
|
|
13
|
+
constants: Set<Binding>;
|
|
14
|
+
/** Whether its arguments use app without importing it. */
|
|
15
|
+
usesGlobalApp: boolean;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Flarum 2.0 registers an extension's settings, permissions and page with the
|
|
19
|
+
* Admin extender, in admin/extend, which is declarative and runs when the admin
|
|
20
|
+
* mounts, rather than through the registry in an initializer.
|
|
21
|
+
*
|
|
22
|
+
* Registrations move only when they would do the same thing from there: each
|
|
23
|
+
* uses nothing the initializer declares, and nothing else registers with the
|
|
24
|
+
* registry as the admin boots, since registering later than that would change
|
|
25
|
+
* their order. Otherwise, all of the extension's registrations stay, with a
|
|
26
|
+
* @TODO saying why.
|
|
27
|
+
*/
|
|
28
|
+
export default class AdminExtender extends BaseUpgradeStep {
|
|
29
|
+
type: string;
|
|
30
|
+
protected moved: string[];
|
|
31
|
+
protected left: string[];
|
|
32
|
+
replacements(file: string): Replacement[];
|
|
33
|
+
targets(): string[];
|
|
34
|
+
gitCommit(): GitCommit;
|
|
35
|
+
pauseMessage(): string;
|
|
36
|
+
protected upgrade(file: string, code: string): Promise<{
|
|
37
|
+
updated: string;
|
|
38
|
+
} | null>;
|
|
39
|
+
/**
|
|
40
|
+
* The registration chains written as statements of an admin initializer,
|
|
41
|
+
* `app.initializers.add(name, () => { ... })`.
|
|
42
|
+
*/
|
|
43
|
+
protected registrations(program: NodePath<t.Program>): Registration[];
|
|
44
|
+
protected registration(statement: NodePath<t.ExpressionStatement>, initializer: NodePath<t.Function>, chain: Chain): Registration;
|
|
45
|
+
/**
|
|
46
|
+
* Why nothing can move because of other code using the registry: in another
|
|
47
|
+
* admin file, or in this one other than in a registration chain or in a
|
|
48
|
+
* function that runs later, through extend() or override().
|
|
49
|
+
*/
|
|
50
|
+
protected otherRegistryUse(program: NodePath<t.Program>, registrations: Registration[], file: string): string | null;
|
|
51
|
+
/**
|
|
52
|
+
* Where the extender goes: the admin/extend the index exports, or a new one.
|
|
53
|
+
*/
|
|
54
|
+
protected target(program: NodePath<t.Program>, file: string): Target | {
|
|
55
|
+
reason: string;
|
|
56
|
+
};
|
|
57
|
+
protected leave(file: string, code: string, registrations: Registration[], blocked: string | null): {
|
|
58
|
+
updated: string;
|
|
59
|
+
} | null;
|
|
60
|
+
protected move(file: string, code: string, program: NodePath<t.Program>, registrations: Registration[], target: Target): Promise<{
|
|
61
|
+
updated: string;
|
|
62
|
+
} | {
|
|
63
|
+
updated: string;
|
|
64
|
+
newFiles: {
|
|
65
|
+
path: string;
|
|
66
|
+
code: string;
|
|
67
|
+
}[];
|
|
68
|
+
} | null>;
|
|
69
|
+
/** The extension's ID, as core makes it from the package name. */
|
|
70
|
+
protected extensionId(): string | null;
|
|
71
|
+
}
|
|
72
|
+
type Chain = {
|
|
73
|
+
root: t.CallExpression;
|
|
74
|
+
calls: t.CallExpression[];
|
|
75
|
+
};
|
|
76
|
+
type Target = {
|
|
77
|
+
file: string;
|
|
78
|
+
/** The code of the extend file the index exports, if it already exists. */
|
|
79
|
+
existing: string | null;
|
|
80
|
+
/** The extenders outside admin the index exported, which the new file spreads. */
|
|
81
|
+
common: {
|
|
82
|
+
source: string;
|
|
83
|
+
declaration: t.ExportNamedDeclaration;
|
|
84
|
+
} | null;
|
|
85
|
+
};
|
|
86
|
+
export {};
|