@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
|
@@ -8,6 +8,7 @@ const import_ext_1 = tslib_1.__importDefault(require("../../steps/upgrade/twopoi
|
|
|
8
8
|
const compat_1 = tslib_1.__importDefault(require("../../steps/upgrade/twopointoh/frontend/export-registry/compat"));
|
|
9
9
|
const usage_of_lazy_modules_1 = tslib_1.__importDefault(require("../../steps/upgrade/twopointoh/frontend/code-splitting/usage-of-lazy-modules"));
|
|
10
10
|
const misc_1 = tslib_1.__importDefault(require("../../steps/upgrade/twopointoh/frontend/misc"));
|
|
11
|
+
const admin_extender_1 = tslib_1.__importDefault(require("../../steps/upgrade/twopointoh/frontend/admin-extender"));
|
|
11
12
|
const format_1 = tslib_1.__importDefault(require("../../steps/upgrade/twopointoh/frontend/format"));
|
|
12
13
|
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
13
14
|
const misc_2 = tslib_1.__importDefault(require("../../steps/upgrade/twopointoh/backend/misc"));
|
|
@@ -17,14 +18,90 @@ const json_api_1 = tslib_1.__importDefault(require("../../steps/upgrade/twopoint
|
|
|
17
18
|
const search_1 = tslib_1.__importDefault(require("../../steps/upgrade/twopointoh/backend/search"));
|
|
18
19
|
const less_1 = tslib_1.__importDefault(require("../../steps/upgrade/twopointoh/less"));
|
|
19
20
|
const phpunit_1 = tslib_1.__importDefault(require("../../steps/upgrade/twopointoh/backend/phpunit"));
|
|
21
|
+
const model_factories_1 = tslib_1.__importDefault(require("../../steps/upgrade/twopointoh/backend/model-factories"));
|
|
20
22
|
const core_1 = require("@oclif/core");
|
|
21
23
|
const email_views_1 = tslib_1.__importDefault(require("../../steps/upgrade/twopointoh/backend/email-views"));
|
|
22
24
|
const intervention_image_1 = tslib_1.__importDefault(require("../../steps/upgrade/twopointoh/backend/intervention-image"));
|
|
25
|
+
const child_process_1 = require("child_process");
|
|
26
|
+
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
27
|
+
const os_1 = tslib_1.__importDefault(require("os"));
|
|
28
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
29
|
+
const simple_git_1 = tslib_1.__importDefault(require("simple-git"));
|
|
23
30
|
class TwoPointOh extends base_command_1.default {
|
|
24
31
|
constructor() {
|
|
25
32
|
super(...arguments);
|
|
26
33
|
this.requireExistingExtension = true;
|
|
27
34
|
this.skipFinalMessage = true;
|
|
35
|
+
/**
|
|
36
|
+
* The steps this run was asked to take, in order.
|
|
37
|
+
*/
|
|
38
|
+
this.scheduled = [];
|
|
39
|
+
this.checkout = null;
|
|
40
|
+
}
|
|
41
|
+
async run() {
|
|
42
|
+
try {
|
|
43
|
+
await super.run();
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
await this.removeCheckout();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async additionalPreRunChecks(extRoot) {
|
|
50
|
+
if (this.flags.check) {
|
|
51
|
+
this.checkout = await this.createCheckout(extRoot);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
stepsPath(requested) {
|
|
55
|
+
var _a, _b;
|
|
56
|
+
return (_b = (_a = this.checkout) === null || _a === void 0 ? void 0 : _a.root) !== null && _b !== void 0 ? _b : requested;
|
|
57
|
+
}
|
|
58
|
+
genIO() {
|
|
59
|
+
var _a;
|
|
60
|
+
// A check asks nothing: there is nothing to confirm.
|
|
61
|
+
if ((_a = this.flags) === null || _a === void 0 ? void 0 : _a.check)
|
|
62
|
+
this.flags['no-interaction'] = true;
|
|
63
|
+
return super.genIO();
|
|
64
|
+
}
|
|
65
|
+
async createCheckout(extRoot) {
|
|
66
|
+
const root = fs_1.default.realpathSync(extRoot);
|
|
67
|
+
const git = (0, simple_git_1.default)(root);
|
|
68
|
+
let repo;
|
|
69
|
+
try {
|
|
70
|
+
repo = (await git.revparse(['--show-toplevel'])).trim();
|
|
71
|
+
}
|
|
72
|
+
catch (_a) {
|
|
73
|
+
this.error('--check runs the upgrade on a copy of the extension in a git worktree, so the extension must be in a git repository.', {
|
|
74
|
+
code: 'FL_ERR',
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
const commit = (await git.revparse(['HEAD'])).trim();
|
|
78
|
+
const dirty = !(await git.status()).isClean();
|
|
79
|
+
const worktree = fs_1.default.mkdtempSync(path_1.default.join(fs_1.default.realpathSync(os_1.default.tmpdir()), 'flarum-upgrade-check-'));
|
|
80
|
+
const branch = `flarum-upgrade-check/${path_1.default.basename(worktree)}`;
|
|
81
|
+
await (0, simple_git_1.default)(repo).raw(['worktree', 'add', '--quiet', '-b', branch, worktree, commit]);
|
|
82
|
+
return { repo, worktree, branch, root: path_1.default.join(worktree, path_1.default.relative(repo, root)), commit, dirty, cwd: process.cwd() };
|
|
83
|
+
}
|
|
84
|
+
/** Remove the worktree and its branch, saying so if it can't. */
|
|
85
|
+
async removeCheckout() {
|
|
86
|
+
const checkout = this.checkout;
|
|
87
|
+
if (!checkout)
|
|
88
|
+
return;
|
|
89
|
+
this.checkout = null;
|
|
90
|
+
// The steps ran from inside the worktree.
|
|
91
|
+
process.chdir(checkout.cwd);
|
|
92
|
+
try {
|
|
93
|
+
await (0, simple_git_1.default)(checkout.repo).raw(['worktree', 'remove', '--force', checkout.worktree]);
|
|
94
|
+
await (0, simple_git_1.default)(checkout.repo).raw(['branch', '-D', checkout.branch]);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
process.stderr.write(`The check could not remove its worktree ${checkout.worktree} and branch ${checkout.branch}: ${error.message}\n` +
|
|
98
|
+
`Remove them with: git worktree remove --force ${checkout.worktree} && git branch -D ${checkout.branch}\n`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/** What a step that committed in the worktree would change, from its commit. */
|
|
102
|
+
wouldChange(sha) {
|
|
103
|
+
const show = (...args) => (0, child_process_1.execFileSync)('git', ['show', '--format=', '--no-color', '--no-ext-diff', ...args, sha], { cwd: this.checkout.worktree, encoding: 'utf8' });
|
|
104
|
+
return { files: show('--name-only').split('\n').filter(Boolean), diff: show() };
|
|
28
105
|
}
|
|
29
106
|
steps(steps, extRoot) {
|
|
30
107
|
// Error.stackTraceLimit = Number.POSITIVE_INFINITY;
|
|
@@ -37,6 +114,7 @@ class TwoPointOh extends base_command_1.default {
|
|
|
37
114
|
import_ext_1.default,
|
|
38
115
|
usage_of_lazy_modules_1.default,
|
|
39
116
|
misc_1.default,
|
|
117
|
+
admin_extender_1.default,
|
|
40
118
|
// Backend
|
|
41
119
|
misc_2.default,
|
|
42
120
|
filesystem_1.default,
|
|
@@ -45,6 +123,7 @@ class TwoPointOh extends base_command_1.default {
|
|
|
45
123
|
json_api_1.default,
|
|
46
124
|
intervention_image_1.default,
|
|
47
125
|
search_1.default,
|
|
126
|
+
model_factories_1.default,
|
|
48
127
|
phpunit_1.default,
|
|
49
128
|
// LESS
|
|
50
129
|
less_1.default,
|
|
@@ -57,23 +136,44 @@ class TwoPointOh extends base_command_1.default {
|
|
|
57
136
|
total++;
|
|
58
137
|
});
|
|
59
138
|
collection = collection.map((Step) => {
|
|
60
|
-
|
|
139
|
+
var _a, _b;
|
|
140
|
+
const step = new Step(this, (_b = (_a = this.checkout) === null || _a === void 0 ? void 0 : _a.root) !== null && _b !== void 0 ? _b : extRoot, stepCount, total, this.flags['force-steps'] || this.flags.check);
|
|
141
|
+
step.checking = Boolean(this.flags.check);
|
|
61
142
|
stepCount++;
|
|
62
143
|
return step;
|
|
63
144
|
});
|
|
64
145
|
if (this.flags.step) {
|
|
65
146
|
const step = Number.parseInt(this.flags.step, 10);
|
|
66
147
|
if (step > 0 && step <= total) {
|
|
148
|
+
this.scheduled = [collection[step - 1]];
|
|
67
149
|
return steps.step(collection[step - 1]);
|
|
68
150
|
}
|
|
69
151
|
this.error(`Invalid step number. Please provide a number between 1 and ${total}`);
|
|
70
152
|
}
|
|
153
|
+
this.scheduled = collection;
|
|
71
154
|
collection.forEach((step) => {
|
|
72
155
|
steps.step(step);
|
|
73
156
|
});
|
|
74
157
|
return steps;
|
|
75
158
|
}
|
|
159
|
+
jsonReport(succeeded, error, messages) {
|
|
160
|
+
return Object.assign(Object.assign(Object.assign({}, super.jsonReport(succeeded, error, messages)), (this.checkout && { check: { commit: this.checkout.commit, uncommittedChangesLeftOut: this.checkout.dirty } })), { steps: this.scheduled.map((step) => (Object.assign({ step: step.number, title: step.type }, this.reported(step)))) });
|
|
161
|
+
}
|
|
162
|
+
/** A step's outcome, as what it would change when checking. */
|
|
163
|
+
reported(step) {
|
|
164
|
+
var _a;
|
|
165
|
+
const outcome = (_a = step.outcome) !== null && _a !== void 0 ? _a : { status: 'not-run' };
|
|
166
|
+
if (!this.checkout || outcome.status !== 'committed')
|
|
167
|
+
return outcome;
|
|
168
|
+
return Object.assign(Object.assign({ status: 'would-change' }, this.wouldChange(outcome.commit.sha)), { notes: outcome.notes, todos: outcome.todos });
|
|
169
|
+
}
|
|
76
170
|
welcomeMessage() {
|
|
171
|
+
if (this.flags.check) {
|
|
172
|
+
return `
|
|
173
|
+
Checking what the Flarum 2.0 upgrade would change in your extension. The steps run on its last commit,
|
|
174
|
+
in a temporary git worktree that is removed afterwards: nothing in your extension changes.
|
|
175
|
+
`;
|
|
176
|
+
}
|
|
77
177
|
const beforeProceeding = chalk_1.default.bold.red('Before proceeding:');
|
|
78
178
|
const command = chalk_1.default.bold.bgYellow.black('fl update js-imports');
|
|
79
179
|
const makeSure = chalk_1.default.bold.dim(`- Make sure your extend.php file directly returns an array of extenders.
|
|
@@ -90,6 +190,25 @@ class TwoPointOh extends base_command_1.default {
|
|
|
90
190
|
`;
|
|
91
191
|
}
|
|
92
192
|
goodbyeMessage() {
|
|
193
|
+
if (this.checkout) {
|
|
194
|
+
const changing = this.scheduled.filter((step) => { var _a; return ((_a = step.outcome) === null || _a === void 0 ? void 0 : _a.status) === 'committed'; });
|
|
195
|
+
const lines = changing.map((step) => {
|
|
196
|
+
const { files } = this.wouldChange(step.outcome.commit.sha);
|
|
197
|
+
return ` - Step ${step.number}, ${step.type} ${chalk_1.default.dim(files.join(', '))}`;
|
|
198
|
+
});
|
|
199
|
+
return [
|
|
200
|
+
changing.length > 0
|
|
201
|
+
? ` ${changing.length} of the steps would change something in ${this.checkout.commit.slice(0, 7)}:`
|
|
202
|
+
: ` None of the steps would change anything in ${this.checkout.commit.slice(0, 7)}.`,
|
|
203
|
+
...lines,
|
|
204
|
+
this.checkout.dirty && ' Changes that were not committed were left out of the check.',
|
|
205
|
+
'',
|
|
206
|
+
' Nothing was changed. Run with --json as well to see each diff, or without --check to make the changes.',
|
|
207
|
+
'',
|
|
208
|
+
]
|
|
209
|
+
.filter((line) => line !== false)
|
|
210
|
+
.join('\n');
|
|
211
|
+
}
|
|
93
212
|
const composer = chalk_1.default.bold.bgYellow('composer update');
|
|
94
213
|
const yarn = chalk_1.default.bold.bgYellow('yarn install');
|
|
95
214
|
return ` Your extension has been successfully upgraded to Flarum 2.0.
|
|
@@ -103,11 +222,18 @@ class TwoPointOh extends base_command_1.default {
|
|
|
103
222
|
}
|
|
104
223
|
exports.default = TwoPointOh;
|
|
105
224
|
TwoPointOh.description = 'Upgrade an extension to Flarum 2.0';
|
|
106
|
-
TwoPointOh.
|
|
225
|
+
TwoPointOh.enableJsonFlag = true;
|
|
226
|
+
TwoPointOh.flags = Object.assign(Object.assign({}, base_command_1.default.flags), { json: core_1.Flags.boolean({
|
|
227
|
+
description: "Report each step's outcome, failures, notes and the @TODOs it left as JSON, for a program driving the upgrade. Implies --no-interaction.",
|
|
228
|
+
required: false,
|
|
229
|
+
}), step: core_1.Flags.string({
|
|
107
230
|
description: 'Optionally specify a specific step to run',
|
|
108
231
|
required: false,
|
|
109
232
|
}), 'force-steps': core_1.Flags.boolean({
|
|
110
233
|
description: 'Force run steps even if already ran',
|
|
111
234
|
required: false,
|
|
235
|
+
}), check: core_1.Flags.boolean({
|
|
236
|
+
description: 'Report what the upgrade would change, such as on an extension already on Flarum 2.0, without changing it: the steps run on the last commit, in a temporary git worktree that is removed afterwards. Implies --force-steps and --no-interaction.',
|
|
237
|
+
required: false,
|
|
112
238
|
}) });
|
|
113
239
|
TwoPointOh.args = [...base_command_1.default.args];
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { Scaffolder } from 'boilersmith/scaffolding/scaffolder';
|
|
2
|
+
/**
|
|
3
|
+
* The SPDX text for a licence, or an empty string when it isn't a known SPDX
|
|
4
|
+
* identifier (e.g. "proprietary" or a free-form "© Some Company").
|
|
5
|
+
*/
|
|
6
|
+
export declare function licenseTextFor(licenseType: unknown): string;
|
|
2
7
|
export declare const EXTENSION_PARAMS: readonly ["packageName", "packageDescription", "packageNamespace", "authorName", "authorEmail", "extensionName", "licenseType", "mainGitBranch", "jsPackageManager", "licenseText", "extensionId", "year", "backendDirectory", "frontendDirectory"];
|
|
3
8
|
export type ExtensionParams = (typeof EXTENSION_PARAMS)[number];
|
|
4
9
|
export declare const EXTENSION_MODULES: readonly ["core", "icon", "admin", "forum", "js", "jsCommon", "css", "locale", "gitConf", "githubActions", "prettier", "typescript", "bundlewatch", "frontendTesting", "backendTesting", "phpstan", "editorConfig", "styleci"];
|
|
@@ -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
|
],
|
|
@@ -8,7 +8,8 @@ export declare class MonorepoSplit implements Step<FlarumProviders> {
|
|
|
8
8
|
composable: boolean;
|
|
9
9
|
exposes: never[];
|
|
10
10
|
force?: boolean;
|
|
11
|
-
|
|
11
|
+
noInteraction?: boolean;
|
|
12
|
+
constructor(force: boolean, noInteraction?: boolean);
|
|
12
13
|
run(fs: Store, paths: Paths, io: IO, _providers: FlarumProviders): Promise<Store>;
|
|
13
14
|
getExposed(): Record<string, unknown>;
|
|
14
15
|
}
|
|
@@ -43,28 +43,43 @@ async function splitAndPush(cwd, splitExecPath, splitPath, remoteName, remoteBra
|
|
|
43
43
|
}
|
|
44
44
|
async function cleanupBranchAndRemote(cwd, name) {
|
|
45
45
|
await (0, simple_git_1.default)(cwd).removeRemote(name);
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
try {
|
|
47
|
+
const tmpBranch = `${name}-tmp`;
|
|
48
|
+
(0, child_process_1.execSync)(`git branch -D ${tmpBranch}`, { cwd, stdio: 'ignore' });
|
|
49
|
+
}
|
|
50
|
+
catch (_a) {
|
|
51
|
+
// A forced split pushes the split sha straight to the remote, so it never
|
|
52
|
+
// creates the temp branch this deletes.
|
|
53
|
+
}
|
|
48
54
|
}
|
|
49
55
|
class MonorepoSplit {
|
|
50
|
-
constructor(force) {
|
|
56
|
+
constructor(force, noInteraction = false) {
|
|
51
57
|
this.type = 'Split monorepo changes to the respective cloned repos.';
|
|
52
58
|
this.composable = false;
|
|
53
59
|
this.exposes = [];
|
|
54
60
|
this.force = force;
|
|
61
|
+
this.noInteraction = noInteraction;
|
|
55
62
|
}
|
|
56
63
|
async run(fs, paths, io, _providers) {
|
|
57
64
|
var _a, _b, _c, _d;
|
|
58
65
|
const platform = process.platform;
|
|
66
|
+
const arch = process.arch;
|
|
59
67
|
if (platform !== 'linux' && platform !== 'darwin') {
|
|
60
68
|
io.error(`Your platform, "${platform}", is not supported. Split can only be run on linux and mac`, true);
|
|
61
69
|
return fs;
|
|
62
70
|
}
|
|
63
|
-
if (this.force &&
|
|
71
|
+
if (this.force &&
|
|
72
|
+
!this.noInteraction &&
|
|
73
|
+
!(await io.getParam({ type: 'confirm', name: 'confirm', initial: false, message: 'Are you sure you want to force split?' }))) {
|
|
64
74
|
io.error('Decided not to force push', true);
|
|
65
75
|
return fs;
|
|
66
76
|
}
|
|
67
|
-
|
|
77
|
+
// Select the appropriate binary based on platform and architecture
|
|
78
|
+
let splitExec;
|
|
79
|
+
splitExec =
|
|
80
|
+
platform === 'darwin' && arch === 'arm64'
|
|
81
|
+
? (0, path_1.resolve)(__dirname, `../../../bin/splitsh-lite-darwin-arm64`)
|
|
82
|
+
: (0, path_1.resolve)(__dirname, `../../../bin/splitsh-lite-${platform}`);
|
|
68
83
|
const target = (_a = paths.requestedDir()) !== null && _a !== void 0 ? _a : paths.package();
|
|
69
84
|
await (0, simple_git_1.default)(target).pull();
|
|
70
85
|
const conf = (0, monorepo_1.getMonorepoConf)(fs, paths);
|
|
@@ -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;
|