@flarum/cli 3.0.11 → 3.0.13
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/bin/libgit2.1.5.dylib +0 -0
- package/bin/splitsh-lite-darwin-arm64 +0 -0
- 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/skeleton/extension/phpstan.neon +0 -1
- package/boilerplate/skeleton/extension/tests/phpunit.integration.xml +1 -0
- package/boilerplate/skeleton/extension/tests/phpunit.unit.xml +2 -1
- 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/monorepo/split.js +1 -1
- 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/monorepo/split.d.ts +2 -1
- package/lib/steps/monorepo/split.js +20 -5
- 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-xml.d.ts +8 -0
- package/lib/steps/upgrade/twopointoh/backend/phpunit-xml.js +33 -0
- package/lib/steps/upgrade/twopointoh/backend/phpunit.d.ts +2 -0
- package/lib/steps/upgrade/twopointoh/backend/phpunit.js +34 -21
- 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 +122 -24
- package/lib/steps/upgrade/twopointoh/dependencies.d.ts +58 -1
- package/lib/steps/upgrade/twopointoh/dependencies.js +343 -15
- 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 +32 -0
- package/lib/steps/upgrade/twopointoh/step-failures.js +54 -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-4-9.d.ts +12 -0
- package/lib/steps/upgrade/twopointoh/typescript-4-9.js +200 -0
- package/lib/utils/ast.js +5 -10
- 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,106 @@ 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_4_9_1 = require("./typescript-4-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
|
+
// Syntax TypeScript 4.9 can't compile, each checked against typescript@4.9.5.
|
|
15
|
+
const NEWER_SYNTAX = [
|
|
16
|
+
[/(?:\bfrom|^\s*import)\s+(["'])[^\n"']*\1\s+with\s*{/, 'import attributes (TypeScript 5.3); use `assert` instead of `with`'],
|
|
17
|
+
[/^\s*export\s+type\s+\*/, '`export type *` (TypeScript 5.0)'],
|
|
18
|
+
[/[,<]\s*const\s+[$A-Z_a-z][\w$]*\s*(?:extends\b|[,=>])/, '`const` type parameters (TypeScript 5.0)'],
|
|
19
|
+
[/^\s*(?:await\s+)?using\s+[$A-Z_a-z][\w$]*\s*[:=]/, '`using` declarations (TypeScript 5.2)'],
|
|
20
|
+
];
|
|
21
|
+
// Packages an extension only needs to build, check and test its frontend.
|
|
22
|
+
const BUILD_TOOLING = new Set([
|
|
23
|
+
'@flarum/jest-config',
|
|
24
|
+
'@flarum/prettier-config',
|
|
25
|
+
'flarum-tsconfig',
|
|
26
|
+
'flarum-webpack-config',
|
|
27
|
+
'prettier',
|
|
28
|
+
'typescript',
|
|
29
|
+
'typescript-coverage-report',
|
|
30
|
+
'webpack',
|
|
31
|
+
'webpack-cli',
|
|
32
|
+
]);
|
|
33
|
+
// The scripts that build and check an extension's typings, as the reusable
|
|
34
|
+
// frontend workflow runs them.
|
|
35
|
+
const TYPINGS_SCRIPTS = ['clean-typings', 'build-typings', 'post-build-typings', 'check-typings', 'check-typings-coverage'];
|
|
36
|
+
// What those scripts run.
|
|
37
|
+
const TYPINGS_TOOLING = ['typescript', 'typescript-coverage-report'];
|
|
38
|
+
/** The package.json `fl init` gives a new extension, for the given package manager. */
|
|
39
|
+
function scaffoldedPackageJson(packageManager) {
|
|
40
|
+
const skeleton = fs_1.default.readFileSync(path_1.default.resolve(__dirname, '../../../../boilerplate/skeleton/extension/js/package.json'), 'utf8');
|
|
41
|
+
// EJS placeholders only appear inside strings, so it parses as JSON.
|
|
42
|
+
return JSON.parse(skeleton.replace(/<%= params\.jsPackageManager %>/g, packageManager));
|
|
43
|
+
}
|
|
44
|
+
const TYPES_COMMENT = [
|
|
45
|
+
'Only load the type packages the code imports. Others, such as the',
|
|
46
|
+
"@types/node webpack brings along, use syntax TypeScript 4.9 can't read.",
|
|
47
|
+
];
|
|
48
|
+
/**
|
|
49
|
+
* The tsconfig.json with an option first in its compilerOptions, and the
|
|
50
|
+
* comment lines above it, indented as the options there are. Null when it has
|
|
51
|
+
* no compilerOptions to put it in.
|
|
52
|
+
*/
|
|
53
|
+
function insertCompilerOption(code, comment, option) {
|
|
54
|
+
var _a;
|
|
55
|
+
const open = code.match(/"compilerOptions"\s*:\s*{/);
|
|
56
|
+
if (!open)
|
|
57
|
+
return null;
|
|
58
|
+
const at = open.index + open[0].length;
|
|
59
|
+
const eol = code.includes('\r\n') ? '\r\n' : '\n';
|
|
60
|
+
const next = code.slice(at).match(/\r?\n([\t ]*)\S/);
|
|
61
|
+
const indent = (_a = next === null || next === void 0 ? void 0 : next[1]) !== null && _a !== void 0 ? _a : ' ';
|
|
62
|
+
const lines = [...(comment !== null && comment !== void 0 ? comment : []).map((line) => `// ${line}`), `${option},`];
|
|
63
|
+
return code.slice(0, at) + lines.map((line) => eol + indent + line).join('') + code.slice(at);
|
|
64
|
+
}
|
|
65
|
+
/** Dependencies in the alphabetical order package managers keep them in. */
|
|
66
|
+
const sortedByName = (dependencies) => Object.fromEntries(Object.entries(dependencies).sort(([a], [b]) => a.localeCompare(b)));
|
|
67
|
+
const isTypeScript = (file) => /\.tsx?$/.test(file);
|
|
68
|
+
/** Whether a version constraint stays within TypeScript 4: `4.9.5`, `^4.5.4`, `~4.7`, `4.x`. */
|
|
69
|
+
const isFourX = (constraint) => {
|
|
70
|
+
const version = constraint.trim().replace(/^\^|^~/, '');
|
|
71
|
+
return version === '4' || version.startsWith('4.');
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* The lowest version a simple constraint allows, as [major, minor, patch]:
|
|
75
|
+
* `^5.1.4`, `~4.7`, `>=8.3`, `5.x`. Null for anything else, such as `*`, a
|
|
76
|
+
* range with `||` or a prerelease.
|
|
77
|
+
*/
|
|
78
|
+
const lowestVersion = (constraint) => {
|
|
79
|
+
const match = constraint.trim().match(/^(?:\^|~|>=|=)?\s*v?(\d+)(?:\.(\d+|x|\*))?(?:\.(\d+|x|\*))?$/);
|
|
80
|
+
return match ? match.slice(1).map((part) => (part && /^\d+$/.test(part) ? Number(part) : 0)) : null;
|
|
81
|
+
};
|
|
82
|
+
/** Whether a constraint asks for a newer version than another does. */
|
|
83
|
+
const asksForNewer = (constraint, than) => {
|
|
84
|
+
const asked = lowestVersion(constraint);
|
|
85
|
+
const other = lowestVersion(than);
|
|
86
|
+
if (!asked || !other)
|
|
87
|
+
return false;
|
|
88
|
+
const differs = asked.findIndex((part, i) => part !== other[i]);
|
|
89
|
+
return differs !== -1 && asked[differs] > other[differs];
|
|
90
|
+
};
|
|
6
91
|
class Dependencies extends base_1.BaseUpgradeStep {
|
|
7
92
|
constructor() {
|
|
8
93
|
super(...arguments);
|
|
9
94
|
this.type = 'Update the extension dependencies to the latest versions.';
|
|
95
|
+
/** The TypeScript the extension asked for, when it was not a 4.x. */
|
|
96
|
+
this.typeScriptWas = null;
|
|
97
|
+
/** What was removed from tsconfig.json, as TypeScript 4.9 doesn't have it. */
|
|
98
|
+
this.removedCompilerOptions = [];
|
|
99
|
+
this.newerSyntax = false;
|
|
100
|
+
/** Constraints left as they were, as they already ask for newer versions. */
|
|
101
|
+
this.keptNewer = [];
|
|
102
|
+
/** Tooling moved from dependencies to devDependencies. */
|
|
103
|
+
this.movedToDev = [];
|
|
104
|
+
/** The typings scripts, and the tooling they run, added to package.json. */
|
|
105
|
+
this.addedTypings = [];
|
|
10
106
|
}
|
|
11
107
|
replacements(file) {
|
|
12
108
|
const replacements = [];
|
|
@@ -16,11 +112,31 @@ class Dependencies extends base_1.BaseUpgradeStep {
|
|
|
16
112
|
break;
|
|
17
113
|
case 'js/package.json':
|
|
18
114
|
replacements.push(this.updatePackageJson());
|
|
115
|
+
break;
|
|
116
|
+
case 'js/tsconfig.json':
|
|
117
|
+
replacements.push(this.updateTsConfig(), this.limitTypes());
|
|
118
|
+
break;
|
|
119
|
+
case 'js/tsconfig.test.json':
|
|
120
|
+
replacements.push(this.testTypes());
|
|
121
|
+
break;
|
|
122
|
+
default:
|
|
123
|
+
if (isTypeScript(file)) {
|
|
124
|
+
replacements.push(this.flagNewerSyntax());
|
|
125
|
+
}
|
|
19
126
|
}
|
|
20
127
|
return replacements;
|
|
21
128
|
}
|
|
22
129
|
targets() {
|
|
23
|
-
return ['composer.json', 'js/package.json'];
|
|
130
|
+
return ['composer.json', 'js/package.json', 'js/tsconfig.json', 'js/tsconfig.test.json', 'js/src/**/*.ts', 'js/src/**/*.tsx'];
|
|
131
|
+
}
|
|
132
|
+
// TypeScript sources are only searched, line by line: parsing them would
|
|
133
|
+
// fail on the very syntax being looked for, and reformatting would change
|
|
134
|
+
// more than the lines flagged.
|
|
135
|
+
advancedContent(file, code) {
|
|
136
|
+
return isTypeScript(file) ? null : super.advancedContent(file, code);
|
|
137
|
+
}
|
|
138
|
+
async updateCode(file, code, updated) {
|
|
139
|
+
return isTypeScript(file) && typeof updated === 'string' ? updated : super.updateCode(file, code, updated);
|
|
24
140
|
}
|
|
25
141
|
gitCommit() {
|
|
26
142
|
return {
|
|
@@ -33,9 +149,34 @@ class Dependencies extends base_1.BaseUpgradeStep {
|
|
|
33
149
|
const composerUpdate = chalk_1.default.bgYellowBright('composer update');
|
|
34
150
|
const yarn = chalk_1.default.bgYellowBright('yarn');
|
|
35
151
|
const npm = chalk_1.default.bgYellowBright('npm install');
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
152
|
+
const notes = [
|
|
153
|
+
this.typeScriptWas &&
|
|
154
|
+
`TypeScript ${this.typeScriptWas} was brought down to ^4.9.5: Flarum 2.0 builds and checks its typings with TypeScript 4.9.`,
|
|
155
|
+
this.removedCompilerOptions.length > 0 &&
|
|
156
|
+
`Removed from js/tsconfig.json, as TypeScript 4.9 does not have them: ${this.removedCompilerOptions.join(', ')}.`,
|
|
157
|
+
this.newerSyntax && 'Some TypeScript uses syntax 4.9 cannot compile; look for the @TODO comments.',
|
|
158
|
+
this.addedTypings.length > 0 && `Added what the reusable frontend workflow runs to build and check typings: ${this.addedTypings.join(', ')}.`,
|
|
159
|
+
this.movedToDev.length > 0 && `Moved to devDependencies, as the extension only builds with them: ${this.movedToDev.join(', ')}.`,
|
|
160
|
+
this.keptNewer.length > 0 && `Kept, as they already ask for newer versions than Flarum 2.0 needs: ${this.keptNewer.join(', ')}.`,
|
|
161
|
+
].filter(Boolean);
|
|
162
|
+
return [
|
|
163
|
+
'Dependencies updated. Please review the changes and update any other dependency versions as needed.',
|
|
164
|
+
...notes,
|
|
165
|
+
`It is recommended to run ${composerInstall} or ${composerUpdate} and ${yarn} or ${npm} now.`,
|
|
166
|
+
'But you may also leave that until the upgrade process is over.',
|
|
167
|
+
].join('\n ');
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Require at least the given version, leaving a constraint that already
|
|
171
|
+
* asks for a newer one as it is.
|
|
172
|
+
*/
|
|
173
|
+
raise(dependencies, name, version) {
|
|
174
|
+
if (asksForNewer(String(dependencies[name]), version)) {
|
|
175
|
+
this.keptNewer.push(`${name} ${dependencies[name]}`);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
dependencies[name] = version;
|
|
179
|
+
}
|
|
39
180
|
}
|
|
40
181
|
updateComposerJson() {
|
|
41
182
|
return (_file, _code, advanced) => {
|
|
@@ -52,7 +193,7 @@ class Dependencies extends base_1.BaseUpgradeStep {
|
|
|
52
193
|
}
|
|
53
194
|
});
|
|
54
195
|
if (key === 'flarum/core') {
|
|
55
|
-
composer.require
|
|
196
|
+
this.raise(composer.require, key, '^2.0.0');
|
|
56
197
|
}
|
|
57
198
|
else if (key.startsWith('flarum/') && value !== '*') {
|
|
58
199
|
composer.require[key] = '*';
|
|
@@ -67,17 +208,20 @@ class Dependencies extends base_1.BaseUpgradeStep {
|
|
|
67
208
|
}
|
|
68
209
|
});
|
|
69
210
|
if (['flarum/core', 'flarum/testing', 'flarum/phpstan'].includes(key)) {
|
|
70
|
-
composer['require-dev']
|
|
211
|
+
this.raise(composer['require-dev'], key, '^2.0.0');
|
|
71
212
|
}
|
|
72
213
|
else if (key.startsWith('flarum/') && value.startsWith('^1')) {
|
|
73
214
|
composer['require-dev'][key] = '*';
|
|
74
215
|
}
|
|
75
216
|
}
|
|
217
|
+
// An extension using newer PHP than Flarum 2.0 needs keeps asking for it.
|
|
76
218
|
if (composer.require.php) {
|
|
77
|
-
composer.require
|
|
219
|
+
this.raise(composer.require, 'php', '^8.3');
|
|
78
220
|
}
|
|
221
|
+
// Flarum 2.0 is stable, so its constraints resolve without changing the
|
|
222
|
+
// extension's minimum stability.
|
|
79
223
|
if (composer.require['fof/extend']) {
|
|
80
|
-
composer.require
|
|
224
|
+
this.raise(composer.require, 'fof/extend', '^2.0.0');
|
|
81
225
|
}
|
|
82
226
|
return {
|
|
83
227
|
updated: composer,
|
|
@@ -86,20 +230,42 @@ class Dependencies extends base_1.BaseUpgradeStep {
|
|
|
86
230
|
}
|
|
87
231
|
updatePackageJson() {
|
|
88
232
|
return (_file, _code, advanced) => {
|
|
233
|
+
var _a;
|
|
89
234
|
advanced = advanced;
|
|
235
|
+
// The versions a new extension is scaffolded with. 3.0.6 is the first
|
|
236
|
+
// flarum-webpack-config that loads an extension's code-split chunks from
|
|
237
|
+
// its own build, and 3.0.7 the first that exports a default export as its
|
|
238
|
+
// value, not under a name taken from its code.
|
|
90
239
|
const map = {
|
|
91
|
-
'flarum-webpack-config': '^3.0.
|
|
240
|
+
'flarum-webpack-config': '^3.0.7',
|
|
92
241
|
'flarum-tsconfig': '^2.0.0',
|
|
93
242
|
'@flarum/jest-config': '^2.0.0',
|
|
94
|
-
|
|
95
|
-
'webpack-cli':
|
|
243
|
+
webpack: '^5.104.1',
|
|
244
|
+
'webpack-cli': '^4.9.1',
|
|
245
|
+
// What Flarum 2.0 builds and checks its typings with.
|
|
246
|
+
typescript: '^4.9.5',
|
|
96
247
|
};
|
|
97
|
-
|
|
98
|
-
for (const
|
|
99
|
-
|
|
100
|
-
|
|
248
|
+
// Extensions list the tooling under either key, often split between both.
|
|
249
|
+
for (const dependencies of [advanced.dependencies, advanced.devDependencies]) {
|
|
250
|
+
for (const key of Object.keys(dependencies || {})) {
|
|
251
|
+
if (key === 'typescript' && !isFourX(String(dependencies[key]))) {
|
|
252
|
+
this.typeScriptWas = dependencies[key];
|
|
253
|
+
}
|
|
254
|
+
// TypeScript is the exception: it comes down to what Flarum checks
|
|
255
|
+
// typings with, whatever the extension asked for.
|
|
256
|
+
if (key === 'typescript') {
|
|
257
|
+
dependencies[key] = map[key];
|
|
258
|
+
}
|
|
259
|
+
else if (map[key]) {
|
|
260
|
+
this.raise(dependencies, key, map[key]);
|
|
261
|
+
}
|
|
101
262
|
}
|
|
102
263
|
}
|
|
264
|
+
if (((_a = advanced.scripts) === null || _a === void 0 ? void 0 : _a['post-build-typings']) === GNU_POST_BUILD_TYPINGS) {
|
|
265
|
+
advanced.scripts['post-build-typings'] = PORTABLE_POST_BUILD_TYPINGS;
|
|
266
|
+
}
|
|
267
|
+
this.addTypingsScripts(advanced);
|
|
268
|
+
this.moveToolingToDevDependencies(advanced);
|
|
103
269
|
// Remove duplicates from devDependencies
|
|
104
270
|
if (advanced.dependencies && advanced.devDependencies) {
|
|
105
271
|
for (const [key] of Object.entries(advanced.dependencies)) {
|
|
@@ -113,5 +279,167 @@ class Dependencies extends base_1.BaseUpgradeStep {
|
|
|
113
279
|
};
|
|
114
280
|
};
|
|
115
281
|
}
|
|
282
|
+
/**
|
|
283
|
+
* Add the scripts that build and check typings, which a new extension is
|
|
284
|
+
* scaffolded with and the reusable frontend workflow runs, along with the
|
|
285
|
+
* tooling they need. Scripts the extension already has are left alone.
|
|
286
|
+
*/
|
|
287
|
+
addTypingsScripts(pkg) {
|
|
288
|
+
const missing = TYPINGS_SCRIPTS.filter((name) => { var _a; return !((_a = pkg.scripts) === null || _a === void 0 ? void 0 : _a[name]); });
|
|
289
|
+
if (missing.length === 0)
|
|
290
|
+
return;
|
|
291
|
+
if (!this.hasTsConfig()) {
|
|
292
|
+
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(', ') + '.');
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const scaffolded = scaffoldedPackageJson(this.packageManager());
|
|
296
|
+
pkg.scripts = Object.assign({}, pkg.scripts);
|
|
297
|
+
for (const name of missing) {
|
|
298
|
+
pkg.scripts[name] = scaffolded.scripts[name];
|
|
299
|
+
}
|
|
300
|
+
const listed = Object.assign(Object.assign({}, pkg.dependencies), pkg.devDependencies);
|
|
301
|
+
const tooling = TYPINGS_TOOLING.filter((name) => !listed[name]);
|
|
302
|
+
if (tooling.length > 0) {
|
|
303
|
+
pkg.devDependencies = sortedByName(Object.assign(Object.assign({}, pkg.devDependencies), Object.fromEntries(tooling.map((name) => [name, scaffolded.devDependencies[name]]))));
|
|
304
|
+
}
|
|
305
|
+
this.addedTypings.push(...missing, ...tooling);
|
|
306
|
+
}
|
|
307
|
+
hasTsConfig() {
|
|
308
|
+
return fs_1.default.existsSync(path_1.default.join(this.root, 'js/tsconfig.json'));
|
|
309
|
+
}
|
|
310
|
+
/** The package manager the extension's frontend uses, by its lockfile. */
|
|
311
|
+
packageManager() {
|
|
312
|
+
return fs_1.default.existsSync(path_1.default.join(this.root, 'js/yarn.lock')) ? 'yarn' : 'npm';
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Flarum 2.0 extensions only build with their tooling: none of it ends up
|
|
316
|
+
* in the bundle, so it belongs in devDependencies. Where the extension lists
|
|
317
|
+
* a tool under both keys, the newer constraint is kept. Other dependencies
|
|
318
|
+
* may be libraries the bundle includes, so they stay where they are.
|
|
319
|
+
*/
|
|
320
|
+
moveToolingToDevDependencies(pkg) {
|
|
321
|
+
const moving = Object.keys(pkg.dependencies || {}).filter((name) => BUILD_TOOLING.has(name) || name.startsWith('@types/'));
|
|
322
|
+
if (moving.length === 0)
|
|
323
|
+
return;
|
|
324
|
+
const devDependencies = Object.assign({}, pkg.devDependencies);
|
|
325
|
+
for (const name of moving) {
|
|
326
|
+
if (!devDependencies[name] || asksForNewer(String(pkg.dependencies[name]), String(devDependencies[name]))) {
|
|
327
|
+
devDependencies[name] = pkg.dependencies[name];
|
|
328
|
+
}
|
|
329
|
+
delete pkg.dependencies[name];
|
|
330
|
+
}
|
|
331
|
+
pkg.devDependencies = sortedByName(devDependencies);
|
|
332
|
+
this.movedToDev.push(...moving);
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Drop the compiler options, and option values, TypeScript 4.9 doesn't have:
|
|
336
|
+
* it refuses a tsconfig.json with any. A tsconfig.json it can read is left
|
|
337
|
+
* exactly as it was.
|
|
338
|
+
*/
|
|
339
|
+
updateTsConfig() {
|
|
340
|
+
return (_file, _code, advanced) => {
|
|
341
|
+
const tsconfig = advanced;
|
|
342
|
+
const options = tsconfig.compilerOptions || {};
|
|
343
|
+
const removed = [];
|
|
344
|
+
for (const [name, value] of Object.entries(options)) {
|
|
345
|
+
const known = Object.prototype.hasOwnProperty.call(typescript_4_9_1.compilerOptions, name) ? typescript_4_9_1.compilerOptions[name] : null;
|
|
346
|
+
if (!known) {
|
|
347
|
+
removed.push(name);
|
|
348
|
+
delete options[name];
|
|
349
|
+
}
|
|
350
|
+
else if (name === 'lib' && Array.isArray(value)) {
|
|
351
|
+
const kept = value.filter((lib) => typeof lib === 'string' && typescript_4_9_1.libs.includes(lib.toLowerCase()));
|
|
352
|
+
for (const lib of value.filter((lib) => !kept.includes(lib))) {
|
|
353
|
+
removed.push(`lib "${lib}"`);
|
|
354
|
+
}
|
|
355
|
+
if (kept.length === 0) {
|
|
356
|
+
delete options.lib;
|
|
357
|
+
}
|
|
358
|
+
else if (kept.length < value.length) {
|
|
359
|
+
options.lib = kept;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
else if (Array.isArray(known) && typeof value === 'string' && !known.includes(value.toLowerCase())) {
|
|
363
|
+
removed.push(`${name} "${value}"`);
|
|
364
|
+
delete options[name];
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
if (Array.isArray(tsconfig.extends)) {
|
|
368
|
+
this.leaveTodo('js/tsconfig.json', 'It extends a list of configs, which TypeScript 4.9 cannot read. Extend one, and move the rest into it.');
|
|
369
|
+
}
|
|
370
|
+
this.removedCompilerOptions.push(...removed);
|
|
371
|
+
return removed.length > 0 ? { updated: tsconfig } : null;
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* TypeScript loads every type package in node_modules unless told which,
|
|
376
|
+
* and 4.9 can't read the @types/node that webpack's minimizer brings along.
|
|
377
|
+
* Load only the ones the code imports. The file is edited as text so its
|
|
378
|
+
* comments stay.
|
|
379
|
+
*/
|
|
380
|
+
limitTypes() {
|
|
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 updated = insertCompilerOption(code, TYPES_COMMENT, '"types": []');
|
|
386
|
+
if (!updated)
|
|
387
|
+
return null;
|
|
388
|
+
const globals = Object.keys(Object.assign(Object.assign({}, this.packageJson().dependencies), this.packageJson().devDependencies)).filter((name) => name.startsWith('@types/') && name !== '@types/jest');
|
|
389
|
+
if (globals.length > 0) {
|
|
390
|
+
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.`);
|
|
391
|
+
}
|
|
392
|
+
return { updated };
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* A test config extending tsconfig.json would no longer load Jest's globals.
|
|
397
|
+
*/
|
|
398
|
+
testTypes() {
|
|
399
|
+
return (_file, code, advanced) => {
|
|
400
|
+
var _a;
|
|
401
|
+
if (((_a = advanced === null || advanced === void 0 ? void 0 : advanced.compilerOptions) === null || _a === void 0 ? void 0 : _a.types) !== undefined)
|
|
402
|
+
return null;
|
|
403
|
+
const pkg = this.packageJson();
|
|
404
|
+
const listed = Object.assign(Object.assign({}, pkg.dependencies), pkg.devDependencies);
|
|
405
|
+
if (!['@flarum/jest-config', 'jest', '@types/jest'].some((name) => listed[name]))
|
|
406
|
+
return null;
|
|
407
|
+
const updated = insertCompilerOption(code, null, '"types": ["jest"]');
|
|
408
|
+
return updated ? { updated } : null;
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
/** The extension's js/package.json, or nothing when it has none. */
|
|
412
|
+
packageJson() {
|
|
413
|
+
try {
|
|
414
|
+
return JSON.parse(fs_1.default.readFileSync(path_1.default.join(this.root, 'js/package.json'), 'utf8'));
|
|
415
|
+
}
|
|
416
|
+
catch (_a) {
|
|
417
|
+
return {};
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Put a @TODO above each line using syntax TypeScript 4.9 can't compile.
|
|
422
|
+
*/
|
|
423
|
+
flagNewerSyntax() {
|
|
424
|
+
return (_file, code) => {
|
|
425
|
+
const flagged = [];
|
|
426
|
+
for (const line of code.split('\n')) {
|
|
427
|
+
for (const [pattern, what] of NEWER_SYNTAX) {
|
|
428
|
+
if (!pattern.test(line))
|
|
429
|
+
continue;
|
|
430
|
+
const indent = line.slice(0, line.length - line.trimStart().length);
|
|
431
|
+
const todo = `${indent}// @TODO: Flarum 2.0 builds with TypeScript 4.9, which has no ${what}.`;
|
|
432
|
+
// An earlier run already flagged it.
|
|
433
|
+
if (flagged.slice(-NEWER_SYNTAX.length).some((previous) => previous.trim() === todo.trim()))
|
|
434
|
+
continue;
|
|
435
|
+
flagged.push(line.endsWith('\r') ? `${todo}\r` : todo);
|
|
436
|
+
this.newerSyntax = true;
|
|
437
|
+
}
|
|
438
|
+
flagged.push(line);
|
|
439
|
+
}
|
|
440
|
+
const updated = flagged.join('\n');
|
|
441
|
+
return updated === code ? null : { updated };
|
|
442
|
+
};
|
|
443
|
+
}
|
|
116
444
|
}
|
|
117
445
|
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 {};
|