@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.
Files changed (76) hide show
  1. package/README.md +33 -1
  2. package/bin/libgit2.1.5.dylib +0 -0
  3. package/bin/splitsh-lite-darwin-arm64 +0 -0
  4. package/boilerplate/skeleton/extension/composer.json +4 -0
  5. package/boilerplate/skeleton/extension/js/package.json +5 -5
  6. package/boilerplate/skeleton/extension/js/tsconfig.json +3 -0
  7. package/boilerplate/skeleton/extension/js/tsconfig.test.json +1 -0
  8. package/boilerplate/skeleton/extension/phpstan.neon +0 -1
  9. package/boilerplate/skeleton/extension/tests/phpunit.integration.xml +1 -0
  10. package/boilerplate/skeleton/extension/tests/phpunit.unit.xml +2 -1
  11. package/boilerplate/stubs/backend/advanced-api-resource.php +3 -3
  12. package/boilerplate/stubs/backend/model-factory.php +17 -0
  13. package/boilerplate/stubs/backend/model.php +3 -0
  14. package/lib/base-command.d.ts +21 -3
  15. package/lib/base-command.js +52 -5
  16. package/lib/commands/make/backend/model.js +10 -0
  17. package/lib/commands/monorepo/split.js +1 -1
  18. package/lib/commands/upgrade/2.0.d.ts +43 -0
  19. package/lib/commands/upgrade/2.0.js +128 -2
  20. package/lib/steps/gen-ext-scaffolder.d.ts +5 -0
  21. package/lib/steps/gen-ext-scaffolder.js +17 -2
  22. package/lib/steps/monorepo/split.d.ts +2 -1
  23. package/lib/steps/monorepo/split.js +20 -5
  24. package/lib/steps/stubs/backend/advanced-api-resource.js +1 -1
  25. package/lib/steps/stubs/backend/model-factory.d.ts +24 -0
  26. package/lib/steps/stubs/backend/model-factory.js +33 -0
  27. package/lib/steps/upgrade/twopointoh/backend/email-views.js +22 -30
  28. package/lib/steps/upgrade/twopointoh/backend/extension-models.d.ts +30 -0
  29. package/lib/steps/upgrade/twopointoh/backend/extension-models.js +34 -0
  30. package/lib/steps/upgrade/twopointoh/backend/json-api.d.ts +6 -0
  31. package/lib/steps/upgrade/twopointoh/backend/json-api.js +14 -0
  32. package/lib/steps/upgrade/twopointoh/backend/misc.d.ts +6 -0
  33. package/lib/steps/upgrade/twopointoh/backend/misc.js +19 -7
  34. package/lib/steps/upgrade/twopointoh/backend/model-factories.d.ts +18 -0
  35. package/lib/steps/upgrade/twopointoh/backend/model-factories.js +73 -0
  36. package/lib/steps/upgrade/twopointoh/backend/phpunit-xml.d.ts +8 -0
  37. package/lib/steps/upgrade/twopointoh/backend/phpunit-xml.js +33 -0
  38. package/lib/steps/upgrade/twopointoh/backend/phpunit.d.ts +2 -0
  39. package/lib/steps/upgrade/twopointoh/backend/phpunit.js +34 -21
  40. package/lib/steps/upgrade/twopointoh/backend/search.d.ts +9 -0
  41. package/lib/steps/upgrade/twopointoh/backend/search.js +28 -10
  42. package/lib/steps/upgrade/twopointoh/base.d.ts +38 -0
  43. package/lib/steps/upgrade/twopointoh/base.js +122 -24
  44. package/lib/steps/upgrade/twopointoh/dependencies.d.ts +58 -1
  45. package/lib/steps/upgrade/twopointoh/dependencies.js +343 -15
  46. package/lib/steps/upgrade/twopointoh/frontend/admin-extender.d.ts +86 -0
  47. package/lib/steps/upgrade/twopointoh/frontend/admin-extender.js +842 -0
  48. package/lib/steps/upgrade/twopointoh/frontend/code-splitting/usage-of-lazy-modules.js +110 -18
  49. package/lib/steps/upgrade/twopointoh/frontend/export-registry/import-ext.js +13 -16
  50. package/lib/steps/upgrade/twopointoh/frontend/misc.d.ts +23 -0
  51. package/lib/steps/upgrade/twopointoh/frontend/misc.js +147 -8
  52. package/lib/steps/upgrade/twopointoh/infra.d.ts +16 -1
  53. package/lib/steps/upgrade/twopointoh/infra.js +87 -5
  54. package/lib/steps/upgrade/twopointoh/step-failures.d.ts +32 -0
  55. package/lib/steps/upgrade/twopointoh/step-failures.js +54 -0
  56. package/lib/steps/upgrade/twopointoh/todos.d.ts +22 -0
  57. package/lib/steps/upgrade/twopointoh/todos.js +57 -0
  58. package/lib/steps/upgrade/twopointoh/typescript-4-9.d.ts +12 -0
  59. package/lib/steps/upgrade/twopointoh/typescript-4-9.js +200 -0
  60. package/lib/utils/ast.js +5 -10
  61. package/lib/utils/babel-ts.d.ts +9 -0
  62. package/lib/utils/babel-ts.js +18 -0
  63. package/lib/utils/composer.d.ts +5 -0
  64. package/lib/utils/composer.js +10 -1
  65. package/lib/utils/terminal.d.ts +13 -0
  66. package/lib/utils/terminal.js +26 -0
  67. package/oclif.manifest.json +1 -1
  68. package/package.json +1 -1
  69. package/php-subsystem/index.php +12 -0
  70. package/php-subsystem/src/NodeUtil.php +19 -9
  71. package/php-subsystem/src/NodeVisitors/AddExtender.php +49 -1
  72. package/php-subsystem/src/Upgrade/TwoPointOh/JsonApi.php +2 -20
  73. package/php-subsystem/src/Upgrade/TwoPointOh/Misc.php +13 -17
  74. package/php-subsystem/src/Upgrade/TwoPointOh/ModelFactories.php +943 -0
  75. package/php-subsystem/src/Upgrade/TwoPointOh/PhpUnit.php +476 -71
  76. 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
- return `Dependencies updated. Please review the changes and update any other dependency versions as needed.
37
- It is recommended to run ${composerInstall} or ${composerUpdate} and ${yarn} or ${npm} now.
38
- But you may also leave that until the upgrade process is over.`;
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[key] = '^2.0.0-beta';
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'][key] = '^2.0.0-beta';
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.php = '^8.2';
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['fof/extend'] = '^2.0.0';
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.0',
240
+ 'flarum-webpack-config': '^3.0.7',
92
241
  'flarum-tsconfig': '^2.0.0',
93
242
  '@flarum/jest-config': '^2.0.0',
94
- 'webpack': "^5.65.0",
95
- 'webpack-cli': "^4.9.1",
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
- const dependencies = advanced.dependencies || advanced.devDependencies;
98
- for (const [key] of Object.entries(dependencies)) {
99
- if (map[key]) {
100
- dependencies[key] = map[key];
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 {};