@multiplatform.one/cli 6.3.0 → 6.4.0
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/lib/bin/multiplatformOne.mjs +13 -2
- package/lib/commands/initApp.mjs +35 -5
- package/lib/commands/updateApp.mjs +260 -0
- package/package.json +2 -2
- package/src/bin/multiplatformOne.ts +30 -5
- package/src/commands/initApp.ts +49 -3
- package/src/commands/updateApp.ts +277 -0
- package/types/bin/multiplatformOne.d.ts.map +1 -1
- package/types/commands/initApp.d.ts +16 -0
- package/types/commands/initApp.d.ts.map +1 -1
- package/types/commands/updateApp.d.ts +24 -0
- package/types/commands/updateApp.d.ts.map +1 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { generateVscodeConfig } from "../generateVscode.mjs";
|
|
2
2
|
import { discoverE2EApp, runE2ESession } from "../commands/e2e.mjs";
|
|
3
3
|
import { init, runModifyStep } from "../commands/init.mjs";
|
|
4
|
-
import { initApp } from "../commands/initApp.mjs";
|
|
4
|
+
import { initApp, readProvenance } from "../commands/initApp.mjs";
|
|
5
|
+
import { updateApp } from "../commands/updateApp.mjs";
|
|
5
6
|
import { createRequire } from "node:module";
|
|
6
7
|
import fs from "node:fs/promises";
|
|
7
8
|
import path from "node:path";
|
|
@@ -491,8 +492,18 @@ program.command("init").option("--universal", "scaffold the universal template (
|
|
|
491
492
|
yes: Boolean(options.yes)
|
|
492
493
|
});
|
|
493
494
|
});
|
|
494
|
-
program.command("update").option("-c, --checkout <branch>", "branch, tag or commit to merge from upstream", "main").option("-r, --remote <url>", "upstream remote URL", "https://gitlab.com/bitspur/multiplatform.one/multiplatform.one.git").description("
|
|
495
|
+
program.command("update").option("-c, --checkout <branch>", "branch, tag or commit to merge from upstream (monorepo forks)", "main").option("-r, --remote <url>", "upstream remote URL (monorepo forks)", "https://gitlab.com/bitspur/multiplatform.one/multiplatform.one.git").option("--skip-install", "skip pnpm install after the update").option("--mpo-version <range>", "semver range for @multiplatform.one/* (default: ^<cli version>)").description("update a scaffolded project to the current template (three-way merge via .mpo.json provenance); monorepo forks fall back to the upstream merge flow").action(async (options) => {
|
|
495
496
|
if (await spawn("git", ["rev-parse", "--is-inside-work-tree"]).then(() => false, () => true)) throw new Error("mpo cannot be updated outside of a git repository");
|
|
497
|
+
if (readProvenance(projectRoot)) {
|
|
498
|
+
await updateApp({
|
|
499
|
+
skipInstall: options.skipInstall,
|
|
500
|
+
version: options.mpoVersion
|
|
501
|
+
});
|
|
502
|
+
try {
|
|
503
|
+
await generateVscodeConfig(projectRoot);
|
|
504
|
+
} catch {}
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
496
507
|
if (await spawn("git", [
|
|
497
508
|
"diff",
|
|
498
509
|
"--cached",
|
package/lib/commands/initApp.mjs
CHANGED
|
@@ -16,12 +16,36 @@ function validateName(name) {
|
|
|
16
16
|
}
|
|
17
17
|
function resolveMpoVersion(explicit) {
|
|
18
18
|
if (explicit) return explicit.startsWith("^") || explicit.startsWith("~") || explicit === "*" ? explicit : `^${explicit}`;
|
|
19
|
+
const version = cliVersion();
|
|
20
|
+
return version ? `^${version}` : "^6.1.0";
|
|
21
|
+
}
|
|
22
|
+
function cliVersion() {
|
|
19
23
|
try {
|
|
20
24
|
const pkgPath = resolve(dirname(fileURLToPath(import.meta.url)), "../../package.json");
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
return JSON.parse(readFileSync(pkgPath, "utf-8")).version;
|
|
26
|
+
} catch {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const PROVENANCE_FILE = ".mpo.json";
|
|
31
|
+
function writeProvenance(targetDir, provenance) {
|
|
32
|
+
writeFileSync(join(targetDir, PROVENANCE_FILE), `${JSON.stringify(provenance, null, 2)}\n`);
|
|
33
|
+
}
|
|
34
|
+
function readProvenance(projectDir) {
|
|
35
|
+
const file = join(projectDir, PROVENANCE_FILE);
|
|
36
|
+
if (!existsSync(file)) return void 0;
|
|
37
|
+
try {
|
|
38
|
+
const raw = JSON.parse(readFileSync(file, "utf-8"));
|
|
39
|
+
if (!raw.template || !raw.cliVersion || !raw.name) return void 0;
|
|
40
|
+
return {
|
|
41
|
+
template: raw.template,
|
|
42
|
+
cliVersion: raw.cliVersion,
|
|
43
|
+
name: raw.name,
|
|
44
|
+
mpoVersion: raw.mpoVersion ?? `^${raw.cliVersion}`
|
|
45
|
+
};
|
|
46
|
+
} catch {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
25
49
|
}
|
|
26
50
|
function templatesRoot(template) {
|
|
27
51
|
return resolve(dirname(fileURLToPath(import.meta.url)), "../../templates", template);
|
|
@@ -121,6 +145,12 @@ async function initApp(nameArg, options = {}) {
|
|
|
121
145
|
}
|
|
122
146
|
console.log(` @multiplatform.one/* → ${vars.MPO_VERSION}\n`);
|
|
123
147
|
writeTree(templatesRoot(template), targetDir, vars);
|
|
148
|
+
writeProvenance(targetDir, {
|
|
149
|
+
template,
|
|
150
|
+
cliVersion: cliVersion() ?? "0.0.0",
|
|
151
|
+
name,
|
|
152
|
+
mpoVersion: vars.MPO_VERSION
|
|
153
|
+
});
|
|
124
154
|
if (!options.skipInstall) {
|
|
125
155
|
console.log("Installing dependencies...");
|
|
126
156
|
await spawn("pnpm", ["install"], {
|
|
@@ -142,4 +172,4 @@ async function initApp(nameArg, options = {}) {
|
|
|
142
172
|
}
|
|
143
173
|
|
|
144
174
|
//#endregion
|
|
145
|
-
export { initApp };
|
|
175
|
+
export { PROVENANCE_FILE, cliVersion, initApp, readProvenance, writeProvenance };
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { cliVersion, initApp, readProvenance, writeProvenance } from "./initApp.mjs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
4
|
+
import spawn from "nano-spawn";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
|
|
7
|
+
//#region src/commands/updateApp.ts
|
|
8
|
+
/**
|
|
9
|
+
* `mpo update` for scaffolded consumer projects — the modern successor of
|
|
10
|
+
* the old cookiecutter flow (init a fresh project, swap your .git in, merge
|
|
11
|
+
* your changes on top), rebuilt with a TRUE three-way merge:
|
|
12
|
+
*
|
|
13
|
+
* base = the ORIGINAL scaffold, regenerated from the CLI version recorded
|
|
14
|
+
* in .mpo.json at init time (published CLIs ship their templates,
|
|
15
|
+
* so `pnpm dlx @multiplatform.one/cli@<old> init` reproduces it)
|
|
16
|
+
* ours = the project's HEAD (all your custom changes)
|
|
17
|
+
* theirs= a fresh scaffold from the CURRENT CLI version
|
|
18
|
+
*
|
|
19
|
+
* `git merge-tree --write-tree --merge-base=<base>` merges template
|
|
20
|
+
* evolution with your customizations; conflicts land in the worktree with
|
|
21
|
+
* normal conflict markers. An `.updateignore` file (one pathspec per line)
|
|
22
|
+
* pins matching paths to your HEAD version, exactly like the old script.
|
|
23
|
+
*/
|
|
24
|
+
async function git(projectDir, args, env) {
|
|
25
|
+
return spawn("git", args, {
|
|
26
|
+
cwd: projectDir,
|
|
27
|
+
env: env ? {
|
|
28
|
+
...process.env,
|
|
29
|
+
...env
|
|
30
|
+
} : void 0
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
async function gitOut(projectDir, args, env) {
|
|
34
|
+
return (await git(projectDir, args, env)).stdout.trim();
|
|
35
|
+
}
|
|
36
|
+
/** Import a directory tree into the project's object database and return a
|
|
37
|
+
* root-less commit for it (temp index; the worktree is never touched). */
|
|
38
|
+
async function commitTreeFromDir(projectDir, dir, message, parent) {
|
|
39
|
+
const indexFile = join(mkdtempSync(join(tmpdir(), "mpo-index-")), "index");
|
|
40
|
+
const env = {
|
|
41
|
+
GIT_INDEX_FILE: indexFile,
|
|
42
|
+
GIT_WORK_TREE: dir
|
|
43
|
+
};
|
|
44
|
+
await git(projectDir, [
|
|
45
|
+
"add",
|
|
46
|
+
"-A",
|
|
47
|
+
"--force",
|
|
48
|
+
"--",
|
|
49
|
+
"."
|
|
50
|
+
], env);
|
|
51
|
+
const tree = await gitOut(projectDir, ["write-tree"], env);
|
|
52
|
+
const commit = await gitOut(projectDir, parent ? [
|
|
53
|
+
"commit-tree",
|
|
54
|
+
tree,
|
|
55
|
+
"-p",
|
|
56
|
+
parent,
|
|
57
|
+
"-m",
|
|
58
|
+
message
|
|
59
|
+
] : [
|
|
60
|
+
"commit-tree",
|
|
61
|
+
tree,
|
|
62
|
+
"-m",
|
|
63
|
+
message
|
|
64
|
+
]);
|
|
65
|
+
rmSync(indexFile, { force: true });
|
|
66
|
+
return commit;
|
|
67
|
+
}
|
|
68
|
+
/** Scaffold a project with a specific published CLI version into tmp and
|
|
69
|
+
* return the generated project dir. Falls back to the local generator when
|
|
70
|
+
* the requested version matches the running CLI (or dlx fails). */
|
|
71
|
+
async function scaffoldBaseline(version, name, template, mpoVersion) {
|
|
72
|
+
const parent = mkdtempSync(join(tmpdir(), "mpo-update-"));
|
|
73
|
+
const cleanup = () => rmSync(parent, {
|
|
74
|
+
recursive: true,
|
|
75
|
+
force: true
|
|
76
|
+
});
|
|
77
|
+
const current = cliVersion();
|
|
78
|
+
const templateFlag = template === "app" ? ["--web"] : ["--universal"];
|
|
79
|
+
if (version !== current) {
|
|
80
|
+
try {
|
|
81
|
+
await spawn("pnpm", [
|
|
82
|
+
"--package",
|
|
83
|
+
`@multiplatform.one/cli@${version}`,
|
|
84
|
+
"dlx",
|
|
85
|
+
"mpo",
|
|
86
|
+
"init",
|
|
87
|
+
name,
|
|
88
|
+
"--yes",
|
|
89
|
+
"--skip-install",
|
|
90
|
+
"--mpo-version",
|
|
91
|
+
mpoVersion,
|
|
92
|
+
...templateFlag
|
|
93
|
+
], {
|
|
94
|
+
cwd: parent,
|
|
95
|
+
stdio: "inherit"
|
|
96
|
+
});
|
|
97
|
+
const dir = join(parent, name);
|
|
98
|
+
if (existsSync(join(dir, "package.json"))) return {
|
|
99
|
+
dir,
|
|
100
|
+
cleanup
|
|
101
|
+
};
|
|
102
|
+
console.warn(`⚠️ dlx scaffold for @multiplatform.one/cli@${version} produced no project;`);
|
|
103
|
+
} catch {
|
|
104
|
+
console.warn(`⚠️ could not scaffold baseline with @multiplatform.one/cli@${version} (network?);`);
|
|
105
|
+
}
|
|
106
|
+
console.warn(" falling back to the current CLI's template as the merge base.");
|
|
107
|
+
}
|
|
108
|
+
const previousCwd = process.cwd();
|
|
109
|
+
process.chdir(parent);
|
|
110
|
+
try {
|
|
111
|
+
await initApp(name, {
|
|
112
|
+
skipInstall: true,
|
|
113
|
+
yes: true,
|
|
114
|
+
template,
|
|
115
|
+
version: mpoVersion
|
|
116
|
+
});
|
|
117
|
+
} finally {
|
|
118
|
+
process.chdir(previousCwd);
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
dir: join(parent, name),
|
|
122
|
+
cleanup
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
async function updateApp(options = {}) {
|
|
126
|
+
const projectDir = await gitOut(process.cwd(), ["rev-parse", "--show-toplevel"]).catch(() => {
|
|
127
|
+
throw new Error("mpo update must run inside a git repository");
|
|
128
|
+
});
|
|
129
|
+
await git(projectDir, ["diff", "--quiet"]).catch(() => {
|
|
130
|
+
throw new Error("mpo update requires a clean working tree (unstaged changes present)");
|
|
131
|
+
});
|
|
132
|
+
await git(projectDir, [
|
|
133
|
+
"diff",
|
|
134
|
+
"--cached",
|
|
135
|
+
"--quiet"
|
|
136
|
+
]).catch(() => {
|
|
137
|
+
throw new Error("mpo update requires a clean working tree (staged changes present)");
|
|
138
|
+
});
|
|
139
|
+
const provenance = readProvenance(projectDir);
|
|
140
|
+
if (!provenance) throw new Error(".mpo.json not found — this project predates scaffold provenance. Create it with { template, cliVersion, name } matching how the project was generated (cliVersion = the @multiplatform.one/cli that scaffolded it), then re-run `mpo update`.");
|
|
141
|
+
const currentVersion = cliVersion() ?? "0.0.0";
|
|
142
|
+
const targetMpoRange = options.version ? options.version.startsWith("^") || options.version.startsWith("~") ? options.version : `^${options.version}` : `^${currentVersion}`;
|
|
143
|
+
console.log(`\nmpo update: ${provenance.cliVersion} → ${currentVersion}`);
|
|
144
|
+
console.log(` template: ${provenance.template}`);
|
|
145
|
+
console.log(` project: ${provenance.name}\n`);
|
|
146
|
+
console.log(`Scaffolding merge base (cli@${provenance.cliVersion})...`);
|
|
147
|
+
const base = await scaffoldBaseline(provenance.cliVersion, provenance.name, provenance.template, provenance.mpoVersion);
|
|
148
|
+
console.log(`Scaffolding update target (cli@${currentVersion})...`);
|
|
149
|
+
const next = await scaffoldBaseline(currentVersion, provenance.name, provenance.template, targetMpoRange);
|
|
150
|
+
writeProvenance(next.dir, {
|
|
151
|
+
template: provenance.template,
|
|
152
|
+
cliVersion: currentVersion,
|
|
153
|
+
name: provenance.name,
|
|
154
|
+
mpoVersion: targetMpoRange
|
|
155
|
+
});
|
|
156
|
+
try {
|
|
157
|
+
const head = await gitOut(projectDir, ["rev-parse", "HEAD"]);
|
|
158
|
+
const baseCommit = await commitTreeFromDir(projectDir, base.dir, `mpo scaffold ${provenance.template}@${provenance.cliVersion}`);
|
|
159
|
+
const nextCommit = await commitTreeFromDir(projectDir, next.dir, `mpo scaffold ${provenance.template}@${currentVersion}`, baseCommit);
|
|
160
|
+
let mergedTree;
|
|
161
|
+
let conflicts = [];
|
|
162
|
+
try {
|
|
163
|
+
mergedTree = (await gitOut(projectDir, [
|
|
164
|
+
"merge-tree",
|
|
165
|
+
"--write-tree",
|
|
166
|
+
"--name-only",
|
|
167
|
+
`--merge-base=${baseCommit}`,
|
|
168
|
+
head,
|
|
169
|
+
nextCommit
|
|
170
|
+
])).split("\n")[0].trim();
|
|
171
|
+
} catch (err) {
|
|
172
|
+
const lines = (err.stdout ?? "").split("\n").filter(Boolean);
|
|
173
|
+
if (!lines.length) throw err;
|
|
174
|
+
mergedTree = lines[0].trim();
|
|
175
|
+
conflicts = lines.slice(1).map((line) => line.trim());
|
|
176
|
+
}
|
|
177
|
+
const finalProvenance = {
|
|
178
|
+
template: provenance.template,
|
|
179
|
+
cliVersion: currentVersion,
|
|
180
|
+
name: provenance.name,
|
|
181
|
+
mpoVersion: targetMpoRange
|
|
182
|
+
};
|
|
183
|
+
if (!conflicts.length) {
|
|
184
|
+
const message = `chore: mpo update ${provenance.cliVersion} → ${currentVersion}`;
|
|
185
|
+
await git(projectDir, [
|
|
186
|
+
"update-ref",
|
|
187
|
+
"HEAD",
|
|
188
|
+
await gitOut(projectDir, [
|
|
189
|
+
"commit-tree",
|
|
190
|
+
mergedTree,
|
|
191
|
+
"-p",
|
|
192
|
+
head,
|
|
193
|
+
"-p",
|
|
194
|
+
nextCommit,
|
|
195
|
+
"-m",
|
|
196
|
+
message
|
|
197
|
+
])
|
|
198
|
+
]);
|
|
199
|
+
await git(projectDir, [
|
|
200
|
+
"reset",
|
|
201
|
+
"--hard",
|
|
202
|
+
"HEAD"
|
|
203
|
+
]);
|
|
204
|
+
writeProvenance(projectDir, finalProvenance);
|
|
205
|
+
await git(projectDir, [
|
|
206
|
+
"add",
|
|
207
|
+
"--",
|
|
208
|
+
".mpo.json"
|
|
209
|
+
]);
|
|
210
|
+
await git(projectDir, [
|
|
211
|
+
"commit",
|
|
212
|
+
"--amend",
|
|
213
|
+
"--no-edit",
|
|
214
|
+
"--quiet"
|
|
215
|
+
]);
|
|
216
|
+
const amended = await gitOut(projectDir, [
|
|
217
|
+
"rev-parse",
|
|
218
|
+
"--short",
|
|
219
|
+
"HEAD"
|
|
220
|
+
]);
|
|
221
|
+
console.log(`\n✅ merged cleanly → ${amended} ("${message}")`);
|
|
222
|
+
} else {
|
|
223
|
+
await git(projectDir, [
|
|
224
|
+
"read-tree",
|
|
225
|
+
"-u",
|
|
226
|
+
"--reset",
|
|
227
|
+
mergedTree
|
|
228
|
+
]);
|
|
229
|
+
writeProvenance(projectDir, finalProvenance);
|
|
230
|
+
console.log("\n⚠️ merge conflicts — resolve the markers, then commit:");
|
|
231
|
+
for (const file of conflicts) console.log(` ${file}`);
|
|
232
|
+
console.log(`\n git add -A && git commit -m "chore: mpo update ${provenance.cliVersion} → ${currentVersion}"`);
|
|
233
|
+
}
|
|
234
|
+
const pins = ["pnpm-lock.yaml"];
|
|
235
|
+
const updateignore = join(projectDir, ".updateignore");
|
|
236
|
+
if (existsSync(updateignore)) for (const line of readFileSync(updateignore, "utf-8").split("\n")) {
|
|
237
|
+
const pattern = line.trim();
|
|
238
|
+
if (pattern && !pattern.startsWith("#")) pins.push(pattern);
|
|
239
|
+
}
|
|
240
|
+
for (const pin of pins) await git(projectDir, [
|
|
241
|
+
"checkout",
|
|
242
|
+
head,
|
|
243
|
+
"--",
|
|
244
|
+
pin
|
|
245
|
+
]).catch(() => {});
|
|
246
|
+
if (!options.skipInstall) {
|
|
247
|
+
console.log("\nInstalling dependencies...");
|
|
248
|
+
await spawn("pnpm", ["install"], {
|
|
249
|
+
cwd: projectDir,
|
|
250
|
+
stdio: "inherit"
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
} finally {
|
|
254
|
+
base.cleanup();
|
|
255
|
+
next.cleanup();
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
//#endregion
|
|
260
|
+
export { updateApp };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@multiplatform.one/cli",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.4.0",
|
|
4
4
|
"description": "multiplatform.one cli — mpo init / create-multiplatform-app",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"create-multiplatform-app",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"nano-spawn": "^2.1.0",
|
|
62
62
|
"yaml": "^2.8.3",
|
|
63
63
|
"yocto-spinner": "^1.1.0",
|
|
64
|
-
"@multiplatform.one/utils": "6.
|
|
64
|
+
"@multiplatform.one/utils": "6.4.0"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"@types/inquirer": "^9.0.9",
|
|
@@ -19,7 +19,8 @@ import YAML from "yaml";
|
|
|
19
19
|
import yoctoSpinner from "yocto-spinner";
|
|
20
20
|
import { discoverE2EApp, runE2ESession } from "../commands/e2e";
|
|
21
21
|
import { init, runModifyStep } from "../commands/init";
|
|
22
|
-
import { initApp } from "../commands/initApp";
|
|
22
|
+
import { initApp, readProvenance } from "../commands/initApp";
|
|
23
|
+
import { updateApp } from "../commands/updateApp";
|
|
23
24
|
|
|
24
25
|
const projectRoot = lookupProjectRoot();
|
|
25
26
|
const availableServices = ["api", "frappe", "solana", "ethereum", "sui"];
|
|
@@ -279,10 +280,22 @@ const defaultUpdateRemote = "https://gitlab.com/bitspur/multiplatform.one/multip
|
|
|
279
280
|
|
|
280
281
|
program
|
|
281
282
|
.command("update")
|
|
282
|
-
.option("-c, --checkout <branch>", "branch, tag or commit to merge from upstream", "main")
|
|
283
|
-
.option("-r, --remote <url>", "upstream remote URL", defaultUpdateRemote)
|
|
284
|
-
.
|
|
285
|
-
.
|
|
283
|
+
.option("-c, --checkout <branch>", "branch, tag or commit to merge from upstream (monorepo forks)", "main")
|
|
284
|
+
.option("-r, --remote <url>", "upstream remote URL (monorepo forks)", defaultUpdateRemote)
|
|
285
|
+
.option("--skip-install", "skip pnpm install after the update")
|
|
286
|
+
.option(
|
|
287
|
+
"--mpo-version <range>",
|
|
288
|
+
"semver range for @multiplatform.one/* (default: ^<cli version>)",
|
|
289
|
+
)
|
|
290
|
+
.description(
|
|
291
|
+
"update a scaffolded project to the current template (three-way merge via .mpo.json provenance); monorepo forks fall back to the upstream merge flow",
|
|
292
|
+
)
|
|
293
|
+
.action(async (options: {
|
|
294
|
+
checkout: string;
|
|
295
|
+
remote: string;
|
|
296
|
+
skipInstall?: boolean;
|
|
297
|
+
mpoVersion?: string;
|
|
298
|
+
}) => {
|
|
286
299
|
// Update prechecks: must be in a git repo
|
|
287
300
|
if (
|
|
288
301
|
await spawn("git", ["rev-parse", "--is-inside-work-tree"]).then(
|
|
@@ -292,6 +305,18 @@ program
|
|
|
292
305
|
) {
|
|
293
306
|
throw new Error("mpo cannot be updated outside of a git repository");
|
|
294
307
|
}
|
|
308
|
+
// Scaffolded consumer projects carry .mpo.json provenance — use the
|
|
309
|
+
// copier-style three-way template update. Monorepo forks fall through to
|
|
310
|
+
// the legacy upstream-merge flow below.
|
|
311
|
+
if (readProvenance(projectRoot)) {
|
|
312
|
+
await updateApp({ skipInstall: options.skipInstall, version: options.mpoVersion });
|
|
313
|
+
try {
|
|
314
|
+
await generateVscodeConfig(projectRoot);
|
|
315
|
+
} catch {
|
|
316
|
+
// best-effort
|
|
317
|
+
}
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
295
320
|
// Require clean working tree (no staged or unstaged changes)
|
|
296
321
|
if (
|
|
297
322
|
await spawn("git", ["diff", "--cached", "--quiet"]).then(
|
package/src/commands/initApp.ts
CHANGED
|
@@ -67,14 +67,54 @@ function resolveMpoVersion(explicit?: string): string {
|
|
|
67
67
|
? explicit
|
|
68
68
|
: `^${explicit}`;
|
|
69
69
|
}
|
|
70
|
+
const version = cliVersion();
|
|
71
|
+
return version ? `^${version}` : "^6.1.0";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function cliVersion(): string | undefined {
|
|
70
75
|
try {
|
|
71
76
|
const pkgPath = resolve(dirname(fileURLToPath(import.meta.url)), "../../package.json");
|
|
72
77
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8")) as { version?: string };
|
|
73
|
-
|
|
78
|
+
return pkg.version;
|
|
79
|
+
} catch {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Scaffold provenance (the copier-answers equivalent): which template, which
|
|
86
|
+
* CLI version and which vars produced this project. `mpo update` regenerates
|
|
87
|
+
* the ORIGINAL scaffold from this record to use as the merge base for a
|
|
88
|
+
* three-way template update.
|
|
89
|
+
*/
|
|
90
|
+
export interface MpoProvenance {
|
|
91
|
+
template: InitAppTemplate;
|
|
92
|
+
cliVersion: string;
|
|
93
|
+
name: string;
|
|
94
|
+
mpoVersion: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const PROVENANCE_FILE = ".mpo.json";
|
|
98
|
+
|
|
99
|
+
export function writeProvenance(targetDir: string, provenance: MpoProvenance): void {
|
|
100
|
+
writeFileSync(join(targetDir, PROVENANCE_FILE), `${JSON.stringify(provenance, null, 2)}\n`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function readProvenance(projectDir: string): MpoProvenance | undefined {
|
|
104
|
+
const file = join(projectDir, PROVENANCE_FILE);
|
|
105
|
+
if (!existsSync(file)) return undefined;
|
|
106
|
+
try {
|
|
107
|
+
const raw = JSON.parse(readFileSync(file, "utf-8")) as Partial<MpoProvenance>;
|
|
108
|
+
if (!raw.template || !raw.cliVersion || !raw.name) return undefined;
|
|
109
|
+
return {
|
|
110
|
+
template: raw.template,
|
|
111
|
+
cliVersion: raw.cliVersion,
|
|
112
|
+
name: raw.name,
|
|
113
|
+
mpoVersion: raw.mpoVersion ?? `^${raw.cliVersion}`,
|
|
114
|
+
};
|
|
74
115
|
} catch {
|
|
75
|
-
|
|
116
|
+
return undefined;
|
|
76
117
|
}
|
|
77
|
-
return "^6.1.0";
|
|
78
118
|
}
|
|
79
119
|
|
|
80
120
|
function templatesRoot(template: InitAppTemplate): string {
|
|
@@ -203,6 +243,12 @@ export async function initApp(
|
|
|
203
243
|
console.log(` @multiplatform.one/* → ${vars.MPO_VERSION}\n`);
|
|
204
244
|
|
|
205
245
|
writeTree(templatesRoot(template), targetDir, vars);
|
|
246
|
+
writeProvenance(targetDir, {
|
|
247
|
+
template,
|
|
248
|
+
cliVersion: cliVersion() ?? "0.0.0",
|
|
249
|
+
name,
|
|
250
|
+
mpoVersion: vars.MPO_VERSION,
|
|
251
|
+
});
|
|
206
252
|
|
|
207
253
|
if (!options.skipInstall) {
|
|
208
254
|
console.log("Installing dependencies...");
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mpo update` for scaffolded consumer projects — the modern successor of
|
|
3
|
+
* the old cookiecutter flow (init a fresh project, swap your .git in, merge
|
|
4
|
+
* your changes on top), rebuilt with a TRUE three-way merge:
|
|
5
|
+
*
|
|
6
|
+
* base = the ORIGINAL scaffold, regenerated from the CLI version recorded
|
|
7
|
+
* in .mpo.json at init time (published CLIs ship their templates,
|
|
8
|
+
* so `pnpm dlx @multiplatform.one/cli@<old> init` reproduces it)
|
|
9
|
+
* ours = the project's HEAD (all your custom changes)
|
|
10
|
+
* theirs= a fresh scaffold from the CURRENT CLI version
|
|
11
|
+
*
|
|
12
|
+
* `git merge-tree --write-tree --merge-base=<base>` merges template
|
|
13
|
+
* evolution with your customizations; conflicts land in the worktree with
|
|
14
|
+
* normal conflict markers. An `.updateignore` file (one pathspec per line)
|
|
15
|
+
* pins matching paths to your HEAD version, exactly like the old script.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
19
|
+
import { tmpdir } from "node:os";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
import spawn from "nano-spawn";
|
|
22
|
+
import { cliVersion, initApp, readProvenance, writeProvenance } from "./initApp";
|
|
23
|
+
|
|
24
|
+
export interface UpdateAppOptions {
|
|
25
|
+
/** Skip pnpm install after the merge. */
|
|
26
|
+
skipInstall?: boolean;
|
|
27
|
+
/** Override the semver range written for @multiplatform.one/* deps. */
|
|
28
|
+
version?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function git(projectDir: string, args: string[], env?: Record<string, string>) {
|
|
32
|
+
return spawn("git", args, {
|
|
33
|
+
cwd: projectDir,
|
|
34
|
+
env: env ? { ...process.env, ...env } : undefined,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function gitOut(projectDir: string, args: string[], env?: Record<string, string>) {
|
|
39
|
+
const result = await git(projectDir, args, env);
|
|
40
|
+
return result.stdout.trim();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Import a directory tree into the project's object database and return a
|
|
44
|
+
* root-less commit for it (temp index; the worktree is never touched). */
|
|
45
|
+
async function commitTreeFromDir(
|
|
46
|
+
projectDir: string,
|
|
47
|
+
dir: string,
|
|
48
|
+
message: string,
|
|
49
|
+
parent?: string,
|
|
50
|
+
): Promise<string> {
|
|
51
|
+
const indexFile = join(mkdtempSync(join(tmpdir(), "mpo-index-")), "index");
|
|
52
|
+
const env = { GIT_INDEX_FILE: indexFile, GIT_WORK_TREE: dir };
|
|
53
|
+
await git(projectDir, ["add", "-A", "--force", "--", "."], env);
|
|
54
|
+
const tree = await gitOut(projectDir, ["write-tree"], env);
|
|
55
|
+
const commitArgs = parent
|
|
56
|
+
? ["commit-tree", tree, "-p", parent, "-m", message]
|
|
57
|
+
: ["commit-tree", tree, "-m", message];
|
|
58
|
+
const commit = await gitOut(projectDir, commitArgs);
|
|
59
|
+
rmSync(indexFile, { force: true });
|
|
60
|
+
return commit;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Scaffold a project with a specific published CLI version into tmp and
|
|
64
|
+
* return the generated project dir. Falls back to the local generator when
|
|
65
|
+
* the requested version matches the running CLI (or dlx fails). */
|
|
66
|
+
async function scaffoldBaseline(
|
|
67
|
+
version: string,
|
|
68
|
+
name: string,
|
|
69
|
+
template: "universal" | "app",
|
|
70
|
+
mpoVersion: string,
|
|
71
|
+
): Promise<{ dir: string; cleanup: () => void }> {
|
|
72
|
+
const parent = mkdtempSync(join(tmpdir(), "mpo-update-"));
|
|
73
|
+
const cleanup = () => rmSync(parent, { recursive: true, force: true });
|
|
74
|
+
const current = cliVersion();
|
|
75
|
+
const templateFlag = template === "app" ? ["--web"] : ["--universal"];
|
|
76
|
+
if (version !== current) {
|
|
77
|
+
try {
|
|
78
|
+
await spawn(
|
|
79
|
+
"pnpm",
|
|
80
|
+
[
|
|
81
|
+
"--package",
|
|
82
|
+
`@multiplatform.one/cli@${version}`,
|
|
83
|
+
"dlx",
|
|
84
|
+
"mpo",
|
|
85
|
+
"init",
|
|
86
|
+
name,
|
|
87
|
+
"--yes",
|
|
88
|
+
"--skip-install",
|
|
89
|
+
"--mpo-version",
|
|
90
|
+
mpoVersion,
|
|
91
|
+
...templateFlag,
|
|
92
|
+
],
|
|
93
|
+
{ cwd: parent, stdio: "inherit" },
|
|
94
|
+
);
|
|
95
|
+
const dir = join(parent, name);
|
|
96
|
+
if (existsSync(join(dir, "package.json"))) return { dir, cleanup };
|
|
97
|
+
console.warn(`⚠️ dlx scaffold for @multiplatform.one/cli@${version} produced no project;`);
|
|
98
|
+
} catch {
|
|
99
|
+
console.warn(
|
|
100
|
+
`⚠️ could not scaffold baseline with @multiplatform.one/cli@${version} (network?);`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
console.warn(" falling back to the current CLI's template as the merge base.");
|
|
104
|
+
}
|
|
105
|
+
const previousCwd = process.cwd();
|
|
106
|
+
process.chdir(parent);
|
|
107
|
+
try {
|
|
108
|
+
await initApp(name, {
|
|
109
|
+
skipInstall: true,
|
|
110
|
+
yes: true,
|
|
111
|
+
template,
|
|
112
|
+
version: mpoVersion,
|
|
113
|
+
});
|
|
114
|
+
} finally {
|
|
115
|
+
process.chdir(previousCwd);
|
|
116
|
+
}
|
|
117
|
+
return { dir: join(parent, name), cleanup };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export async function updateApp(options: UpdateAppOptions = {}): Promise<void> {
|
|
121
|
+
const projectDir = await gitOut(process.cwd(), ["rev-parse", "--show-toplevel"]).catch(() => {
|
|
122
|
+
throw new Error("mpo update must run inside a git repository");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Clean tree required — the merge lands in the worktree.
|
|
126
|
+
await git(projectDir, ["diff", "--quiet"]).catch(() => {
|
|
127
|
+
throw new Error("mpo update requires a clean working tree (unstaged changes present)");
|
|
128
|
+
});
|
|
129
|
+
await git(projectDir, ["diff", "--cached", "--quiet"]).catch(() => {
|
|
130
|
+
throw new Error("mpo update requires a clean working tree (staged changes present)");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const provenance = readProvenance(projectDir);
|
|
134
|
+
if (!provenance) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
".mpo.json not found — this project predates scaffold provenance. " +
|
|
137
|
+
"Create it with { template, cliVersion, name } matching how the project " +
|
|
138
|
+
"was generated (cliVersion = the @multiplatform.one/cli that scaffolded it), " +
|
|
139
|
+
"then re-run `mpo update`.",
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const currentVersion = cliVersion() ?? "0.0.0";
|
|
144
|
+
const targetMpoRange = options.version
|
|
145
|
+
? options.version.startsWith("^") || options.version.startsWith("~")
|
|
146
|
+
? options.version
|
|
147
|
+
: `^${options.version}`
|
|
148
|
+
: `^${currentVersion}`;
|
|
149
|
+
|
|
150
|
+
console.log(`\nmpo update: ${provenance.cliVersion} → ${currentVersion}`);
|
|
151
|
+
console.log(` template: ${provenance.template}`);
|
|
152
|
+
console.log(` project: ${provenance.name}\n`);
|
|
153
|
+
|
|
154
|
+
// 1. Regenerate the ORIGINAL scaffold (merge base).
|
|
155
|
+
console.log(`Scaffolding merge base (cli@${provenance.cliVersion})...`);
|
|
156
|
+
const base = await scaffoldBaseline(
|
|
157
|
+
provenance.cliVersion,
|
|
158
|
+
provenance.name,
|
|
159
|
+
provenance.template,
|
|
160
|
+
provenance.mpoVersion,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
// 2. Generate the CURRENT scaffold (theirs).
|
|
164
|
+
console.log(`Scaffolding update target (cli@${currentVersion})...`);
|
|
165
|
+
const next = await scaffoldBaseline(
|
|
166
|
+
currentVersion,
|
|
167
|
+
provenance.name,
|
|
168
|
+
provenance.template,
|
|
169
|
+
targetMpoRange,
|
|
170
|
+
);
|
|
171
|
+
writeProvenance(next.dir, {
|
|
172
|
+
template: provenance.template,
|
|
173
|
+
cliVersion: currentVersion,
|
|
174
|
+
name: provenance.name,
|
|
175
|
+
mpoVersion: targetMpoRange,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
// 3. Three-way merge: base → HEAD (ours) vs base → next (theirs).
|
|
180
|
+
const head = await gitOut(projectDir, ["rev-parse", "HEAD"]);
|
|
181
|
+
const baseCommit = await commitTreeFromDir(
|
|
182
|
+
projectDir,
|
|
183
|
+
base.dir,
|
|
184
|
+
`mpo scaffold ${provenance.template}@${provenance.cliVersion}`,
|
|
185
|
+
);
|
|
186
|
+
const nextCommit = await commitTreeFromDir(
|
|
187
|
+
projectDir,
|
|
188
|
+
next.dir,
|
|
189
|
+
`mpo scaffold ${provenance.template}@${currentVersion}`,
|
|
190
|
+
baseCommit,
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
let mergedTree: string;
|
|
194
|
+
let conflicts: string[] = [];
|
|
195
|
+
try {
|
|
196
|
+
const out = await gitOut(projectDir, [
|
|
197
|
+
"merge-tree",
|
|
198
|
+
"--write-tree",
|
|
199
|
+
"--name-only",
|
|
200
|
+
`--merge-base=${baseCommit}`,
|
|
201
|
+
head,
|
|
202
|
+
nextCommit,
|
|
203
|
+
]);
|
|
204
|
+
mergedTree = out.split("\n")[0]!.trim();
|
|
205
|
+
} catch (err) {
|
|
206
|
+
// Exit code 1 = conflicts; stdout still carries tree + conflict names.
|
|
207
|
+
const stdout = (err as { stdout?: string }).stdout ?? "";
|
|
208
|
+
const lines = stdout.split("\n").filter(Boolean);
|
|
209
|
+
if (!lines.length) throw err;
|
|
210
|
+
mergedTree = lines[0]!.trim();
|
|
211
|
+
conflicts = lines.slice(1).map((line) => line.trim());
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// The merged .mpo.json can interleave ours/theirs lines (add/add
|
|
215
|
+
// two-way merge) — the post-merge provenance is always written
|
|
216
|
+
// authoritatively below.
|
|
217
|
+
const finalProvenance = {
|
|
218
|
+
template: provenance.template,
|
|
219
|
+
cliVersion: currentVersion,
|
|
220
|
+
name: provenance.name,
|
|
221
|
+
mpoVersion: targetMpoRange,
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
if (!conflicts.length) {
|
|
225
|
+
const message = `chore: mpo update ${provenance.cliVersion} → ${currentVersion}`;
|
|
226
|
+
const mergeCommit = await gitOut(projectDir, [
|
|
227
|
+
"commit-tree",
|
|
228
|
+
mergedTree,
|
|
229
|
+
"-p",
|
|
230
|
+
head,
|
|
231
|
+
"-p",
|
|
232
|
+
nextCommit,
|
|
233
|
+
"-m",
|
|
234
|
+
message,
|
|
235
|
+
]);
|
|
236
|
+
await git(projectDir, ["update-ref", "HEAD", mergeCommit]);
|
|
237
|
+
await git(projectDir, ["reset", "--hard", "HEAD"]);
|
|
238
|
+
writeProvenance(projectDir, finalProvenance);
|
|
239
|
+
await git(projectDir, ["add", "--", ".mpo.json"]);
|
|
240
|
+
await git(projectDir, ["commit", "--amend", "--no-edit", "--quiet"]);
|
|
241
|
+
const amended = await gitOut(projectDir, ["rev-parse", "--short", "HEAD"]);
|
|
242
|
+
console.log(`\n✅ merged cleanly → ${amended} ("${message}")`);
|
|
243
|
+
} else {
|
|
244
|
+
// Land the merged tree (with conflict markers) in index + worktree for
|
|
245
|
+
// manual resolution.
|
|
246
|
+
await git(projectDir, ["read-tree", "-u", "--reset", mergedTree]);
|
|
247
|
+
writeProvenance(projectDir, finalProvenance);
|
|
248
|
+
console.log("\n⚠️ merge conflicts — resolve the markers, then commit:");
|
|
249
|
+
for (const file of conflicts) console.log(` ${file}`);
|
|
250
|
+
console.log(
|
|
251
|
+
`\n git add -A && git commit -m "chore: mpo update ${provenance.cliVersion} → ${currentVersion}"`,
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// 4. .updateignore: pin matching paths to the pre-update HEAD (always
|
|
256
|
+
// includes pnpm-lock.yaml — the install below regenerates it).
|
|
257
|
+
const pins = ["pnpm-lock.yaml"];
|
|
258
|
+
const updateignore = join(projectDir, ".updateignore");
|
|
259
|
+
if (existsSync(updateignore)) {
|
|
260
|
+
for (const line of readFileSync(updateignore, "utf-8").split("\n")) {
|
|
261
|
+
const pattern = line.trim();
|
|
262
|
+
if (pattern && !pattern.startsWith("#")) pins.push(pattern);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
for (const pin of pins) {
|
|
266
|
+
await git(projectDir, ["checkout", head, "--", pin]).catch(() => {});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (!options.skipInstall) {
|
|
270
|
+
console.log("\nInstalling dependencies...");
|
|
271
|
+
await spawn("pnpm", ["install"], { cwd: projectDir, stdio: "inherit" });
|
|
272
|
+
}
|
|
273
|
+
} finally {
|
|
274
|
+
base.cleanup();
|
|
275
|
+
next.cleanup();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"multiplatformOne.d.ts","sourceRoot":"","sources":["../../src/bin/multiplatformOne.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"multiplatformOne.d.ts","sourceRoot":"","sources":["../../src/bin/multiplatformOne.ts"],"names":[],"mappings":"AA2FA,gHAAgH;AAChH,MAAM,MAAM,wBAAwB,GAAG;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,kGAAkG;IAClG,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gGAAgG;IAChG,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC,CAAC"}
|
|
@@ -16,6 +16,22 @@ export interface InitAppOptions {
|
|
|
16
16
|
/** Accept defaults instead of prompting (non-interactive / CI). */
|
|
17
17
|
yes?: boolean;
|
|
18
18
|
}
|
|
19
|
+
export declare function cliVersion(): string | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Scaffold provenance (the copier-answers equivalent): which template, which
|
|
22
|
+
* CLI version and which vars produced this project. `mpo update` regenerates
|
|
23
|
+
* the ORIGINAL scaffold from this record to use as the merge base for a
|
|
24
|
+
* three-way template update.
|
|
25
|
+
*/
|
|
26
|
+
export interface MpoProvenance {
|
|
27
|
+
template: InitAppTemplate;
|
|
28
|
+
cliVersion: string;
|
|
29
|
+
name: string;
|
|
30
|
+
mpoVersion: string;
|
|
31
|
+
}
|
|
32
|
+
export declare const PROVENANCE_FILE = ".mpo.json";
|
|
33
|
+
export declare function writeProvenance(targetDir: string, provenance: MpoProvenance): void;
|
|
34
|
+
export declare function readProvenance(projectDir: string): MpoProvenance | undefined;
|
|
19
35
|
/**
|
|
20
36
|
* Scaffold a consumer project (universal by default, web-only with
|
|
21
37
|
* template: "app") that depends on @multiplatform.one/* at semver ranges
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"initApp.d.ts","sourceRoot":"","sources":["../../src/commands/initApp.ts"],"names":[],"mappings":"AAaA;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,KAAK,CAAC;AAElD,MAAM,WAAW,cAAc;IAC7B,2DAA2D;IAC3D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,8EAA8E;IAC9E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,mEAAmE;IACnE,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;
|
|
1
|
+
{"version":3,"file":"initApp.d.ts","sourceRoot":"","sources":["../../src/commands/initApp.ts"],"names":[],"mappings":"AAaA;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,KAAK,CAAC;AAElD,MAAM,WAAW,cAAc;IAC7B,2DAA2D;IAC3D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,8EAA8E;IAC9E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,mEAAmE;IACnE,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AA0CD,wBAAgB,UAAU,IAAI,MAAM,GAAG,SAAS,CAQ/C;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,eAAe,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,eAAe,cAAc,CAAC;AAE3C,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,aAAa,GAAG,IAAI,CAElF;AAED,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAe5E;AAuED;;;;GAIG;AACH,wBAAsB,OAAO,CAC3B,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,IAAI,CAAC,CA6Ef"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mpo update` for scaffolded consumer projects — the modern successor of
|
|
3
|
+
* the old cookiecutter flow (init a fresh project, swap your .git in, merge
|
|
4
|
+
* your changes on top), rebuilt with a TRUE three-way merge:
|
|
5
|
+
*
|
|
6
|
+
* base = the ORIGINAL scaffold, regenerated from the CLI version recorded
|
|
7
|
+
* in .mpo.json at init time (published CLIs ship their templates,
|
|
8
|
+
* so `pnpm dlx @multiplatform.one/cli@<old> init` reproduces it)
|
|
9
|
+
* ours = the project's HEAD (all your custom changes)
|
|
10
|
+
* theirs= a fresh scaffold from the CURRENT CLI version
|
|
11
|
+
*
|
|
12
|
+
* `git merge-tree --write-tree --merge-base=<base>` merges template
|
|
13
|
+
* evolution with your customizations; conflicts land in the worktree with
|
|
14
|
+
* normal conflict markers. An `.updateignore` file (one pathspec per line)
|
|
15
|
+
* pins matching paths to your HEAD version, exactly like the old script.
|
|
16
|
+
*/
|
|
17
|
+
export interface UpdateAppOptions {
|
|
18
|
+
/** Skip pnpm install after the merge. */
|
|
19
|
+
skipInstall?: boolean;
|
|
20
|
+
/** Override the semver range written for @multiplatform.one/* deps. */
|
|
21
|
+
version?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function updateApp(options?: UpdateAppOptions): Promise<void>;
|
|
24
|
+
//# sourceMappingURL=updateApp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"updateApp.d.ts","sourceRoot":"","sources":["../../src/commands/updateApp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAQH,MAAM,WAAW,gBAAgB;IAC/B,yCAAyC;IACzC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA2FD,wBAAsB,SAAS,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6J7E"}
|