@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.genExtScaffolder = exports.EXTENSION_MODULES = exports.EXTENSION_PARAMS = void 0;
|
|
3
|
+
exports.genExtScaffolder = exports.EXTENSION_MODULES = exports.EXTENSION_PARAMS = exports.licenseTextFor = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const scaffolder_1 = require("boilersmith/scaffolding/scaffolder");
|
|
6
6
|
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
@@ -15,6 +15,17 @@ const string_1 = tslib_1.__importDefault(require("string"));
|
|
|
15
15
|
function assertUnreachable(_x) {
|
|
16
16
|
throw new Error("Didn't expect to get here");
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* The SPDX text for a licence, or an empty string when it isn't a known SPDX
|
|
20
|
+
* identifier (e.g. "proprietary" or a free-form "© Some Company").
|
|
21
|
+
*/
|
|
22
|
+
function licenseTextFor(licenseType) {
|
|
23
|
+
if (typeof licenseType !== 'string' || !licenseType)
|
|
24
|
+
return '';
|
|
25
|
+
const id = [...simple_1.default].find((l) => l.toLowerCase() === licenseType.toLowerCase());
|
|
26
|
+
return id ? require(`spdx-license-list/licenses/${id}`).licenseText : '';
|
|
27
|
+
}
|
|
28
|
+
exports.licenseTextFor = licenseTextFor;
|
|
18
29
|
exports.EXTENSION_PARAMS = [
|
|
19
30
|
'packageName',
|
|
20
31
|
'packageDescription',
|
|
@@ -157,7 +168,7 @@ function paramNamesToDef(name) {
|
|
|
157
168
|
return {
|
|
158
169
|
name,
|
|
159
170
|
uses: ['licenseType'],
|
|
160
|
-
compute: async (_paths, licenseType) =>
|
|
171
|
+
compute: async (_paths, licenseType) => licenseTextFor(licenseType),
|
|
161
172
|
};
|
|
162
173
|
case 'extensionId':
|
|
163
174
|
return {
|
|
@@ -609,11 +620,15 @@ function moduleNameToDef(name) {
|
|
|
609
620
|
'autoload-dev.psr-4.${params.packageNamespace}\\Tests\\',
|
|
610
621
|
'scripts.test',
|
|
611
622
|
'scripts.test:unit',
|
|
623
|
+
'scripts.test:unit:filter',
|
|
612
624
|
'scripts.test:integration',
|
|
625
|
+
'scripts.test:integration:filter',
|
|
613
626
|
'scripts.test:setup',
|
|
614
627
|
'scripts-descriptions.test',
|
|
615
628
|
'scripts-descriptions.test:unit',
|
|
629
|
+
'scripts-descriptions.test:unit:filter',
|
|
616
630
|
'scripts-descriptions.test:integration',
|
|
631
|
+
'scripts-descriptions.test:integration:filter',
|
|
617
632
|
'scripts-descriptions.test:setup',
|
|
618
633
|
'require-dev.flarum/testing',
|
|
619
634
|
],
|
|
@@ -49,7 +49,7 @@ class GenerateAdvancedApiResourceStub extends php_base_1.BasePhpStubStep {
|
|
|
49
49
|
type: 'multiselect',
|
|
50
50
|
message: 'Endpoints',
|
|
51
51
|
choices: [
|
|
52
|
-
{ title: 'index', value: '
|
|
52
|
+
{ title: 'index', value: 'list', selected: true },
|
|
53
53
|
{ title: 'show', value: 'show', selected: true },
|
|
54
54
|
{ title: 'create', value: 'create', selected: true },
|
|
55
55
|
{ title: 'update', value: 'update', selected: true },
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Validator } from '../../../utils/validation';
|
|
2
|
+
import { BasePhpStubStep } from '../php-base';
|
|
3
|
+
/**
|
|
4
|
+
* The factory a model's tests fill it from. It sits next to the model, named
|
|
5
|
+
* after it, which is where Flarum looks for a model's factory.
|
|
6
|
+
*/
|
|
7
|
+
export declare class GenerateModelFactoryStub extends BasePhpStubStep {
|
|
8
|
+
type: string;
|
|
9
|
+
protected schema: {
|
|
10
|
+
recommendedSubdir: string;
|
|
11
|
+
sourceFile: string;
|
|
12
|
+
params: ({
|
|
13
|
+
name: string;
|
|
14
|
+
type: string;
|
|
15
|
+
message: string;
|
|
16
|
+
validate: typeof Validator.className;
|
|
17
|
+
} | {
|
|
18
|
+
name: string;
|
|
19
|
+
type: string;
|
|
20
|
+
message: string;
|
|
21
|
+
validate?: undefined;
|
|
22
|
+
})[];
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GenerateModelFactoryStub = void 0;
|
|
4
|
+
const validation_1 = require("../../../utils/validation");
|
|
5
|
+
const php_base_1 = require("../php-base");
|
|
6
|
+
/**
|
|
7
|
+
* The factory a model's tests fill it from. It sits next to the model, named
|
|
8
|
+
* after it, which is where Flarum looks for a model's factory.
|
|
9
|
+
*/
|
|
10
|
+
class GenerateModelFactoryStub extends php_base_1.BasePhpStubStep {
|
|
11
|
+
constructor() {
|
|
12
|
+
super(...arguments);
|
|
13
|
+
this.type = 'Generate Model Factory Class';
|
|
14
|
+
this.schema = {
|
|
15
|
+
recommendedSubdir: '',
|
|
16
|
+
sourceFile: 'backend/model-factory.php',
|
|
17
|
+
params: [
|
|
18
|
+
{
|
|
19
|
+
name: 'className',
|
|
20
|
+
type: 'text',
|
|
21
|
+
message: 'Factory class name',
|
|
22
|
+
validate: validation_1.Validator.className,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'classNamespace',
|
|
26
|
+
type: 'text',
|
|
27
|
+
message: 'Class Namespace',
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.GenerateModelFactoryStub = GenerateModelFactoryStub;
|
|
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
4
|
const ejs_1 = tslib_1.__importDefault(require("ejs"));
|
|
5
5
|
const base_1 = require("../base");
|
|
6
|
+
/** Where a view lives: `acme-thing::emails.launched` is in `.../emails/launched.blade.php`. */
|
|
7
|
+
const viewFile = (view) => '/' + view.split('::').pop().replace(/\./g, '/') + '.blade.php';
|
|
6
8
|
class EmailViews extends base_1.BaseUpgradeStep {
|
|
7
9
|
constructor() {
|
|
8
10
|
super(...arguments);
|
|
@@ -28,38 +30,28 @@ class EmailViews extends base_1.BaseUpgradeStep {
|
|
|
28
30
|
};
|
|
29
31
|
},
|
|
30
32
|
async (file, code) => {
|
|
31
|
-
if (Object.keys(this.collected).length === 0)
|
|
32
|
-
return null;
|
|
33
|
-
const fileName = file.split('/').pop();
|
|
34
33
|
const newFiles = [];
|
|
35
|
-
Object.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
for (const views of Object.values(this.collected)) {
|
|
35
|
+
// A notification that has both views already has nothing to convert,
|
|
36
|
+
// such as one already upgraded to 2.0.
|
|
37
|
+
if (!views || Boolean(views.html) === Boolean(views.text))
|
|
38
|
+
continue;
|
|
39
|
+
const view = views.html || views.text;
|
|
40
|
+
// The view's own file, by its full path: on 2.x the html and plain
|
|
41
|
+
// views of a notification have the same file name.
|
|
42
|
+
if (!file.endsWith(viewFile(view)))
|
|
43
|
+
continue;
|
|
44
|
+
const viewFileName = view.split('::')[1].split('.').pop();
|
|
45
|
+
for (const viewType of ['text', 'html']) {
|
|
46
|
+
const templateContent = this.fsEditor.read(__dirname + this.templates[viewType]);
|
|
47
|
+
const viewTypePath = viewType === 'text' ? 'plain' : 'html';
|
|
48
|
+
newFiles.push({
|
|
49
|
+
path: file.split('/views/')[0] + '/views/email/' + viewTypePath + '/' + viewFileName + '.blade.php',
|
|
50
|
+
code: ejs_1.default.render(templateContent, { content: code.trimEnd() }),
|
|
51
|
+
});
|
|
41
52
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
['text', 'html'].forEach((view) => {
|
|
45
|
-
const viewType = view;
|
|
46
|
-
const viewPath = this.collected[classFile][viewType] || this.collected[classFile][viewType === 'html' ? 'text' : 'html'];
|
|
47
|
-
const viewFileName = viewPath.split('::')[1].split('.').pop();
|
|
48
|
-
if (fileName === `${viewFileName}.blade.php`) {
|
|
49
|
-
const templateContent = this.fsEditor.read(__dirname + this.templates[viewType]);
|
|
50
|
-
const viewTypePath = viewType === 'text' ? 'plain' : 'html';
|
|
51
|
-
newFiles.push({
|
|
52
|
-
path: file.split('/views/')[0] + '/views/email/' + viewTypePath + '/' + viewFileName + '.blade.php',
|
|
53
|
-
code: ejs_1.default.render(templateContent, { content: code.trimEnd() }),
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
return {
|
|
59
|
-
updated: code,
|
|
60
|
-
delete: newFiles.length > 0,
|
|
61
|
-
newFiles,
|
|
62
|
-
};
|
|
53
|
+
}
|
|
54
|
+
return newFiles.length > 0 ? { updated: code, delete: true, newFiles } : null;
|
|
63
55
|
},
|
|
64
56
|
];
|
|
65
57
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { PhpProvider } from '../../../../providers/php-provider';
|
|
2
|
+
export type ExtensionModel = {
|
|
3
|
+
file: string;
|
|
4
|
+
class: string;
|
|
5
|
+
table: string;
|
|
6
|
+
hasFactory: boolean;
|
|
7
|
+
};
|
|
8
|
+
export type Column = {
|
|
9
|
+
name: string;
|
|
10
|
+
type: string;
|
|
11
|
+
nullable: boolean;
|
|
12
|
+
autoIncrement: boolean;
|
|
13
|
+
references: string | null;
|
|
14
|
+
default?: {
|
|
15
|
+
value: unknown;
|
|
16
|
+
};
|
|
17
|
+
values?: string[];
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* The extension's models, each with its table, and the models that have, or
|
|
21
|
+
* are given, a factory, by table: core's and the extension's.
|
|
22
|
+
*/
|
|
23
|
+
export declare function extensionModels(root: string, php: PhpProvider): {
|
|
24
|
+
models: ExtensionModel[];
|
|
25
|
+
byTable: Record<string, string>;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* The tables the extension's migrations leave, by name.
|
|
29
|
+
*/
|
|
30
|
+
export declare function migrationTables(root: string, php: PhpProvider): Record<string, Column[]>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.migrationTables = exports.extensionModels = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
6
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
7
|
+
const globby_1 = tslib_1.__importDefault(require("globby"));
|
|
8
|
+
const model_name_1 = require("../../../../utils/model-name");
|
|
9
|
+
const read = (root, pattern) => Object.fromEntries(globby_1.default.sync(pattern, { cwd: root }).map((file) => [file, fs_1.default.readFileSync(path_1.default.join(root, file), 'utf8')]));
|
|
10
|
+
/**
|
|
11
|
+
* The extension's models, each with its table, and the models that have, or
|
|
12
|
+
* are given, a factory, by table: core's and the extension's.
|
|
13
|
+
*/
|
|
14
|
+
function extensionModels(root, php) {
|
|
15
|
+
const result = php.run('upgrade.2-0.models', { data: { files: read(root, 'src/**/*.php') } });
|
|
16
|
+
// A model without $table has the one Eloquent names after its class.
|
|
17
|
+
const models = result.models.map((model) => {
|
|
18
|
+
var _a;
|
|
19
|
+
return (Object.assign(Object.assign({}, model), { table: (_a = model.table) !== null && _a !== void 0 ? _a : (0, model_name_1.pluralSnakeCaseModel)(model.class.split('\\').pop()) }));
|
|
20
|
+
});
|
|
21
|
+
return {
|
|
22
|
+
models,
|
|
23
|
+
byTable: Object.assign(Object.assign({}, result.core), Object.fromEntries(models.map((model) => [model.table, model.class]))),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
exports.extensionModels = extensionModels;
|
|
27
|
+
/**
|
|
28
|
+
* The tables the extension's migrations leave, by name.
|
|
29
|
+
*/
|
|
30
|
+
function migrationTables(root, php) {
|
|
31
|
+
const migrations = Object.fromEntries(Object.entries(read(root, 'migrations/*.php')).map(([file, code]) => [path_1.default.basename(file), code]));
|
|
32
|
+
return php.run('upgrade.2-0.migrations-schema', { data: { migrations } }).tables;
|
|
33
|
+
}
|
|
34
|
+
exports.migrationTables = migrationTables;
|
|
@@ -7,6 +7,12 @@ export default class JsonApi extends BaseUpgradeStep {
|
|
|
7
7
|
before(file: string, code: string, _advanced: AdvancedContent): void;
|
|
8
8
|
replacements(file: string): Replacement[];
|
|
9
9
|
targets(): string[];
|
|
10
|
+
/**
|
|
11
|
+
* 2.0's user resource has avatarSrcset next to avatarUrl, and browsers
|
|
12
|
+
* prefer it, so code that only sets the URL still shows a user's uploaded
|
|
13
|
+
* avatar on most screens.
|
|
14
|
+
*/
|
|
15
|
+
flagAvatarUrl(): Replacement;
|
|
10
16
|
gitCommit(): GitCommit;
|
|
11
17
|
pauseMessage(): string;
|
|
12
18
|
}
|
|
@@ -41,6 +41,7 @@ class JsonApi extends base_1.BaseUpgradeStep {
|
|
|
41
41
|
return [];
|
|
42
42
|
let collected = null;
|
|
43
43
|
return [
|
|
44
|
+
this.flagAvatarUrl(),
|
|
44
45
|
async (file, code) => {
|
|
45
46
|
const output = this.php.run('upgrade.2-0.json-api', { file, code });
|
|
46
47
|
collected = output.collected;
|
|
@@ -85,6 +86,19 @@ class JsonApi extends base_1.BaseUpgradeStep {
|
|
|
85
86
|
targets() {
|
|
86
87
|
return ['src/**/*', 'extend.php'];
|
|
87
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* 2.0's user resource has avatarSrcset next to avatarUrl, and browsers
|
|
91
|
+
* prefer it, so code that only sets the URL still shows a user's uploaded
|
|
92
|
+
* avatar on most screens.
|
|
93
|
+
*/
|
|
94
|
+
flagAvatarUrl() {
|
|
95
|
+
return (file, code) => {
|
|
96
|
+
if (/["']avatarUrl["']/.test(code)) {
|
|
97
|
+
this.leaveTodo(file, "It sets avatarUrl. 2.0's API also has avatarSrcset, which browsers prefer over the URL: set it too, or a user's uploaded avatar still shows.");
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
88
102
|
gitCommit() {
|
|
89
103
|
return {
|
|
90
104
|
message: 'chore(2.0): JSON:API changes',
|
|
@@ -4,5 +4,11 @@ export default class MiscBackendChanges extends BaseUpgradeStep {
|
|
|
4
4
|
replacements(file: string): Replacement[];
|
|
5
5
|
targets(): string[];
|
|
6
6
|
gitCommit(): GitCommit;
|
|
7
|
+
/**
|
|
8
|
+
* Since Laravel 11, changing a column drops every attribute the change
|
|
9
|
+
* leaves out, so a migration's ->change() must repeat the whole column
|
|
10
|
+
* definition. Only the author knows the column's full definition.
|
|
11
|
+
*/
|
|
12
|
+
flagColumnChanges(): Replacement;
|
|
7
13
|
pauseMessage(): string;
|
|
8
14
|
}
|
|
@@ -3,6 +3,7 @@ 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 MODIFYING_COLUMNS = 'https://laravel.com/docs/11.x/upgrade#modifying-columns';
|
|
6
7
|
class MiscBackendChanges extends base_1.BaseUpgradeStep {
|
|
7
8
|
constructor() {
|
|
8
9
|
super(...arguments);
|
|
@@ -11,6 +12,9 @@ class MiscBackendChanges extends base_1.BaseUpgradeStep {
|
|
|
11
12
|
replacements(file) {
|
|
12
13
|
if (!file.endsWith('.php'))
|
|
13
14
|
return [];
|
|
15
|
+
if (file.startsWith('migrations/')) {
|
|
16
|
+
return [this.flagColumnChanges()];
|
|
17
|
+
}
|
|
14
18
|
return [
|
|
15
19
|
(file, code) => ({
|
|
16
20
|
updated: this.php.run('upgrade.2-0.misc', { file, code }).code,
|
|
@@ -18,7 +22,7 @@ class MiscBackendChanges extends base_1.BaseUpgradeStep {
|
|
|
18
22
|
];
|
|
19
23
|
}
|
|
20
24
|
targets() {
|
|
21
|
-
return ['extend.php', 'src/**/*'];
|
|
25
|
+
return ['extend.php', 'src/**/*', 'migrations/**/*.php'];
|
|
22
26
|
}
|
|
23
27
|
gitCommit() {
|
|
24
28
|
return {
|
|
@@ -26,17 +30,25 @@ class MiscBackendChanges extends base_1.BaseUpgradeStep {
|
|
|
26
30
|
description: 'Miscellaneous backend changes',
|
|
27
31
|
};
|
|
28
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Since Laravel 11, changing a column drops every attribute the change
|
|
35
|
+
* leaves out, so a migration's ->change() must repeat the whole column
|
|
36
|
+
* definition. Only the author knows the column's full definition.
|
|
37
|
+
*/
|
|
38
|
+
flagColumnChanges() {
|
|
39
|
+
return (file, code) => {
|
|
40
|
+
if (/->\s*change\s*\(\s*\)/.test(code)) {
|
|
41
|
+
this.leaveTodo(file, `It changes a column with ->change(), which now drops every attribute the change leaves out: repeat the column's whole definition. ${MODIFYING_COLUMNS}`);
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
29
46
|
pauseMessage() {
|
|
30
47
|
const link = 'https://docs.flarum.org/2.x/extend/update-2_0#miscellaneous-1';
|
|
31
48
|
const readMore = chalk_1.default.dim(`Read more: ${link}`);
|
|
32
|
-
const requiresManualChangeLink = 'https://laravel.com/docs/11.x/upgrade#modifying-columns';
|
|
33
49
|
return `Various breaking backend changes have been made in 2.0. The tool has attempted to update your code accordingly.
|
|
34
50
|
Please review the changes and ensure everything is correct.
|
|
35
|
-
${readMore}
|
|
36
|
-
|
|
37
|
-
Additionally, check every reference to a column definition change in your migrations, and make sure
|
|
38
|
-
they are using the entire column definition. For more details, see:
|
|
39
|
-
${chalk_1.default.underline(requiresManualChangeLink)}`;
|
|
51
|
+
${readMore}`;
|
|
40
52
|
}
|
|
41
53
|
}
|
|
42
54
|
exports.default = MiscBackendChanges;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { BaseUpgradeStep, GitCommit, Replacement } from '../base';
|
|
2
|
+
/**
|
|
3
|
+
* Give each of the extension's models a factory, written from what its
|
|
4
|
+
* migrations say about the model's table, so that tests naming the model in
|
|
5
|
+
* prepareDatabase() need only give the columns they care about.
|
|
6
|
+
*/
|
|
7
|
+
export default class ModelFactories extends BaseUpgradeStep {
|
|
8
|
+
type: string;
|
|
9
|
+
/** The factories this step wrote. */
|
|
10
|
+
protected written: string[];
|
|
11
|
+
private extension;
|
|
12
|
+
replacements(file: string): Replacement[];
|
|
13
|
+
targets(): string[];
|
|
14
|
+
gitCommit(): GitCommit;
|
|
15
|
+
pauseMessage(): string;
|
|
16
|
+
addFactory(): Replacement;
|
|
17
|
+
private found;
|
|
18
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
5
|
+
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
6
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
7
|
+
const base_1 = require("../base");
|
|
8
|
+
const extension_models_1 = require("./extension-models");
|
|
9
|
+
/**
|
|
10
|
+
* Give each of the extension's models a factory, written from what its
|
|
11
|
+
* migrations say about the model's table, so that tests naming the model in
|
|
12
|
+
* prepareDatabase() need only give the columns they care about.
|
|
13
|
+
*/
|
|
14
|
+
class ModelFactories extends base_1.BaseUpgradeStep {
|
|
15
|
+
constructor() {
|
|
16
|
+
super(...arguments);
|
|
17
|
+
this.type = 'Model factories for tests';
|
|
18
|
+
/** The factories this step wrote. */
|
|
19
|
+
this.written = [];
|
|
20
|
+
this.extension = null;
|
|
21
|
+
}
|
|
22
|
+
replacements(file) {
|
|
23
|
+
return file.endsWith('.php') ? [this.addFactory()] : [];
|
|
24
|
+
}
|
|
25
|
+
targets() {
|
|
26
|
+
return ['src/**/*.php'];
|
|
27
|
+
}
|
|
28
|
+
gitCommit() {
|
|
29
|
+
return {
|
|
30
|
+
message: 'chore(2.0): model factories for tests',
|
|
31
|
+
description: "Flarum 2.0 tests take the columns they leave out from the model's factory.",
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
pauseMessage() {
|
|
35
|
+
const link = 'https://docs.flarum.org/2.x/extend/testing#model-factories';
|
|
36
|
+
return [
|
|
37
|
+
`Each model has a factory now. Written by this step: ${this.written.join(', ') || 'none'}.`,
|
|
38
|
+
'Tests that give prepareDatabase() a model class, rather than its table, take the columns they leave out from it.',
|
|
39
|
+
'Look for the @TODO comments on columns the migrations gave no default.',
|
|
40
|
+
chalk_1.default.underline(link),
|
|
41
|
+
].join('\n ');
|
|
42
|
+
}
|
|
43
|
+
addFactory() {
|
|
44
|
+
return (file, code) => {
|
|
45
|
+
var _a;
|
|
46
|
+
const { models, byTable, tables } = this.found();
|
|
47
|
+
const model = models.find((candidate) => candidate.file === file);
|
|
48
|
+
if (!model)
|
|
49
|
+
return null;
|
|
50
|
+
const factory = path_1.default.posix.join(path_1.default.posix.dirname(file), `${model.class.split('\\').pop()}Factory.php`);
|
|
51
|
+
const hasFactoryClass = fs_1.default.existsSync(path_1.default.join(this.root, factory));
|
|
52
|
+
if (model.hasFactory && hasFactoryClass)
|
|
53
|
+
return null;
|
|
54
|
+
const result = this.php.run('upgrade.2-0.model-factory', {
|
|
55
|
+
file,
|
|
56
|
+
code,
|
|
57
|
+
data: { columns: (_a = tables[model.table]) !== null && _a !== void 0 ? _a : null, models: byTable },
|
|
58
|
+
});
|
|
59
|
+
// A factory the extension already has is its own.
|
|
60
|
+
if (hasFactoryClass) {
|
|
61
|
+
return { updated: result.code };
|
|
62
|
+
}
|
|
63
|
+
this.written.push(factory);
|
|
64
|
+
return { updated: result.code, newFiles: [{ path: factory, code: result.factory }] };
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
found() {
|
|
68
|
+
var _a;
|
|
69
|
+
(_a = this.extension) !== null && _a !== void 0 ? _a : (this.extension = Object.assign(Object.assign({}, (0, extension_models_1.extensionModels)(this.root, this.php)), { tables: (0, extension_models_1.migrationTables)(this.root, this.php) }));
|
|
70
|
+
return this.extension;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
exports.default = ModelFactories;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { BaseUpgradeStep, GitCommit, Replacement } from '../base';
|
|
2
2
|
export default class PhpUnit extends BaseUpgradeStep {
|
|
3
3
|
type: string;
|
|
4
|
+
/** Core's models and the extension's, by table, for prepareDatabase() to name. */
|
|
5
|
+
private models;
|
|
4
6
|
replacements(file: string): Replacement[];
|
|
5
7
|
targets(): string[];
|
|
6
8
|
gitCommit(): GitCommit;
|
|
@@ -4,17 +4,24 @@ const tslib_1 = require("tslib");
|
|
|
4
4
|
const base_1 = require("../base");
|
|
5
5
|
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
6
6
|
const phpunit_xml_1 = require("./phpunit-xml");
|
|
7
|
+
const extension_models_1 = require("./extension-models");
|
|
7
8
|
class PhpUnit extends base_1.BaseUpgradeStep {
|
|
8
9
|
constructor() {
|
|
9
10
|
super(...arguments);
|
|
10
|
-
this.type = 'PHPUnit 9 to
|
|
11
|
+
this.type = 'PHPUnit 9 to 12 changes';
|
|
12
|
+
/** Core's models and the extension's, by table, for prepareDatabase() to name. */
|
|
13
|
+
this.models = null;
|
|
11
14
|
}
|
|
12
15
|
replacements(file) {
|
|
13
16
|
if (file.endsWith('.php')) {
|
|
14
17
|
return [
|
|
15
|
-
(_file, code) =>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
(_file, code) => {
|
|
19
|
+
var _a;
|
|
20
|
+
(_a = this.models) !== null && _a !== void 0 ? _a : (this.models = (0, extension_models_1.extensionModels)(this.root, this.php).byTable);
|
|
21
|
+
return {
|
|
22
|
+
updated: this.php.run('upgrade.2-0.phpunit', { file, code, data: { models: this.models } }).code,
|
|
23
|
+
};
|
|
24
|
+
},
|
|
18
25
|
];
|
|
19
26
|
}
|
|
20
27
|
// The XML rewrite previously sat behind the .php guard above (making it
|
|
@@ -34,18 +41,19 @@ class PhpUnit extends base_1.BaseUpgradeStep {
|
|
|
34
41
|
}
|
|
35
42
|
gitCommit() {
|
|
36
43
|
return {
|
|
37
|
-
message: 'chore(2.0): Backend tests + PHPUnit 9 to
|
|
38
|
-
description: 'Flarum 2.0 uses PHPUnit
|
|
44
|
+
message: 'chore(2.0): Backend tests + PHPUnit 9 to 12 changes',
|
|
45
|
+
description: 'Flarum 2.0 uses PHPUnit 12 and encourages use of model factories for easier cross-database testing.',
|
|
39
46
|
};
|
|
40
47
|
}
|
|
41
48
|
pauseMessage() {
|
|
42
49
|
const links = [
|
|
43
50
|
'https://github.com/sebastianbergmann/phpunit/blob/9.6/DEPRECATIONS.md',
|
|
44
51
|
'https://github.com/sebastianbergmann/phpunit/blob/10.5/DEPRECATIONS.md',
|
|
45
|
-
'https://github.com/sebastianbergmann/phpunit/blob/11.
|
|
52
|
+
'https://github.com/sebastianbergmann/phpunit/blob/11.5/DEPRECATIONS.md',
|
|
53
|
+
'https://github.com/sebastianbergmann/phpunit/blob/12.5/DEPRECATIONS.md',
|
|
46
54
|
];
|
|
47
55
|
const dbLink = 'https://docs.flarum.org/2.x/extend/testing#model-factories';
|
|
48
|
-
return `Flarum 2.0 uses PHPUnit
|
|
56
|
+
return `Flarum 2.0 uses PHPUnit 12. The tool has applied the most significant changes, and left @TODO comments on what it could not, but you might still run into other deprecations.
|
|
49
57
|
Please refer to the following links for more information:
|
|
50
58
|
${links.map((link) => chalk_1.default.underline(link)).join('\n ')}
|
|
51
59
|
|
|
@@ -21,6 +21,15 @@ export default class Search extends BaseUpgradeStep {
|
|
|
21
21
|
protected searchers: Record<string, string>;
|
|
22
22
|
before(file: string, code: string, _advanced: AdvancedContent): void;
|
|
23
23
|
replacements(_file: string): Replacement[];
|
|
24
|
+
/**
|
|
25
|
+
* The translations a gambit's stub uses: its key, and a key-value gambit's
|
|
26
|
+
* hint, which the search shows beside it. Only the author can say what the
|
|
27
|
+
* hint should be, so it is left for them to write.
|
|
28
|
+
*/
|
|
29
|
+
gambitTranslations(gambit: Gambit): {
|
|
30
|
+
key: string;
|
|
31
|
+
value: string;
|
|
32
|
+
}[];
|
|
24
33
|
targets(): string[];
|
|
25
34
|
gitCommit(): GitCommit;
|
|
26
35
|
pauseMessage(): string;
|
|
@@ -9,6 +9,10 @@ const gambit_1 = require("../../../stubs/frontend/gambit");
|
|
|
9
9
|
const search_gambit_1 = require("../../../js/search-gambit");
|
|
10
10
|
const base_2 = require("../../../locale/base");
|
|
11
11
|
const model_name_1 = require("../../../../utils/model-name");
|
|
12
|
+
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
13
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
14
|
+
/** A gambit with an is: pattern, such as is:private, rather than a key and value. */
|
|
15
|
+
const isBoolean = (gambit) => gambit.pattern.includes('is:');
|
|
12
16
|
class Search extends base_1.BaseUpgradeStep {
|
|
13
17
|
constructor() {
|
|
14
18
|
super(...arguments);
|
|
@@ -116,10 +120,6 @@ class Search extends base_1.BaseUpgradeStep {
|
|
|
116
120
|
if (this.gambits.length > 0 && Object.keys(((_a = this.data.collectedData.extendData) === null || _a === void 0 ? void 0 : _a.searchers) || {}).length > 0) {
|
|
117
121
|
const result = await this.command.runSteps(new step_manager_1.StepManager().silentGroup((steps) => {
|
|
118
122
|
this.gambits.forEach((gambit, index) => {
|
|
119
|
-
let gambitKey = gambit.pattern.replace('is:', '');
|
|
120
|
-
if (gambitKey.includes(':')) {
|
|
121
|
-
gambitKey = gambitKey.split(':')[0];
|
|
122
|
-
}
|
|
123
123
|
const relatedSearcher = Object.keys(this.data.collectedData.extendData.searchers).find((searcher) => {
|
|
124
124
|
const filters = this.data.collectedData.extendData.searchers[searcher].filters || [];
|
|
125
125
|
return filters.some((filter) => {
|
|
@@ -130,11 +130,11 @@ class Search extends base_1.BaseUpgradeStep {
|
|
|
130
130
|
if (relatedSearcher) {
|
|
131
131
|
type = (0, model_name_1.pluralKebabCaseModel)(relatedSearcher.split('\\').pop().replace('Searcher', ''));
|
|
132
132
|
}
|
|
133
|
-
steps
|
|
133
|
+
const gambitSteps = steps
|
|
134
134
|
.namedStep(`gambit-${index}`, new gambit_1.GenerateGambitStub(this.command.STUB_PATH, (0, gen_ext_scaffolder_1.genExtScaffolder)()), {}, [], {
|
|
135
135
|
frontend: 'common',
|
|
136
136
|
className: gambit.name,
|
|
137
|
-
type: gambit
|
|
137
|
+
type: isBoolean(gambit) ? 'boolean' : 'key-value',
|
|
138
138
|
filterKey: gambit.filterKey,
|
|
139
139
|
})
|
|
140
140
|
.step(new search_gambit_1.GenerateSearchGambitExtender(), {}, [
|
|
@@ -146,10 +146,9 @@ class Search extends base_1.BaseUpgradeStep {
|
|
|
146
146
|
], {
|
|
147
147
|
frontend: 'common',
|
|
148
148
|
modelType: type,
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
value: gambitKey,
|
|
149
|
+
});
|
|
150
|
+
this.gambitTranslations(gambit).forEach((translation) => {
|
|
151
|
+
gambitSteps.step(new base_2.LocaleStep((0, gen_ext_scaffolder_1.genExtScaffolder)()), {}, [], translation);
|
|
153
152
|
});
|
|
154
153
|
delete this.gambits[index];
|
|
155
154
|
});
|
|
@@ -171,6 +170,25 @@ class Search extends base_1.BaseUpgradeStep {
|
|
|
171
170
|
},
|
|
172
171
|
];
|
|
173
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* The translations a gambit's stub uses: its key, and a key-value gambit's
|
|
175
|
+
* hint, which the search shows beside it. Only the author can say what the
|
|
176
|
+
* hint should be, so it is left for them to write.
|
|
177
|
+
*/
|
|
178
|
+
gambitTranslations(gambit) {
|
|
179
|
+
let gambitKey = gambit.pattern.replace('is:', '');
|
|
180
|
+
if (gambitKey.includes(':')) {
|
|
181
|
+
gambitKey = gambitKey.split(':')[0];
|
|
182
|
+
}
|
|
183
|
+
const translations = [{ key: `lib.gambits.${gambit.filterKey}.key`, value: gambitKey }];
|
|
184
|
+
if (!isBoolean(gambit)) {
|
|
185
|
+
const hint = `lib.gambits.${gambit.filterKey}.hint`;
|
|
186
|
+
const locale = fs_1.default.existsSync(path_1.default.join(this.root, 'resources/locale/en.yml')) ? 'resources/locale/en.yml' : 'locale/en.yml';
|
|
187
|
+
translations.push({ key: hint, value: gambitKey });
|
|
188
|
+
this.leaveTodo(locale, `describe what the ${gambitKey} gambit takes in ${hint}, which the search shows beside it.`);
|
|
189
|
+
}
|
|
190
|
+
return translations;
|
|
191
|
+
}
|
|
174
192
|
targets() {
|
|
175
193
|
return ['src/**/*', 'extend.php'];
|
|
176
194
|
}
|