@multiplatform.one/cli 7.11.0 → 7.16.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/README.md +121 -111
- package/bin/createMultiplatformApp.mjs +39 -37
- package/lib/bin/multiplatformOne.mjs +65 -75
- package/lib/commands/addApp.mjs +78 -0
- package/lib/commands/adoptApp.mjs +11 -7
- package/lib/commands/doctypeTypes.mjs +148 -0
- package/lib/commands/e2e.mjs +2 -2
- package/lib/commands/init.mjs +142 -459
- package/lib/commands/merge.mjs +359 -0
- package/lib/commands/pieces.mjs +236 -0
- package/lib/commands/provenance.mjs +108 -0
- package/lib/commands/scaffold.mjs +105 -0
- package/lib/commands/updateApp.mjs +211 -528
- package/lib/source.json +5 -0
- package/lib/source.mjs +63 -0
- package/package.json +6 -6
- package/scripts/frappe-bootstrap.sh +29 -3
- package/scripts/frappe-dev.sh +1 -2
- package/scripts/frappe-helpers.sh +50 -2
- package/scripts/frappe.sh +5 -0
- package/scripts/scaffold.sh +937 -0
- package/scripts/stamp-source.mjs +51 -0
- package/scripts/stamp-source.spec.ts +86 -0
- package/src/bin/multiplatformOne.ts +116 -226
- package/src/commands/addApp.ts +102 -0
- package/src/commands/adoptApp.spec.ts +5 -1
- package/src/commands/adoptApp.ts +15 -10
- package/src/commands/doctypeTypes.spec.ts +155 -0
- package/src/commands/doctypeTypes.ts +214 -0
- package/src/commands/init.spec.ts +66 -0
- package/src/commands/init.ts +201 -643
- package/src/commands/merge.spec.ts +81 -0
- package/src/commands/merge.ts +411 -0
- package/src/commands/pieces.spec.ts +62 -0
- package/src/commands/pieces.ts +340 -0
- package/src/commands/provenance.spec.ts +92 -0
- package/src/commands/provenance.ts +169 -0
- package/src/commands/scaffold.ts +131 -0
- package/src/commands/updateApp.spec.ts +540 -331
- package/src/commands/updateApp.ts +255 -668
- package/src/source.spec.ts +45 -0
- package/src/source.ts +88 -0
- package/templates/AGENTS.md +161 -0
- package/templates/README.md +62 -0
- package/templates/pieces/frappe/universal/README.md.partial +7 -32
- package/templates/pieces/frappe/universal/env.example.partial +3 -3
- package/templates/pieces/frappe-bench/universal/README.md.partial +28 -0
- package/templates/pieces/{frappe → frappe-bench}/universal/docker/compose.frappe.yaml +2 -2
- package/templates/pieces/tauri/universal/README.md.partial +7 -5
- package/templates/pieces/tauri/universal/apps/__NAME__/src-tauri/src/dev.rs +463 -0
- package/templates/pieces/tauri/universal/apps/__NAME__/src-tauri/src/lib.rs +6 -0
- package/templates/pieces/tauri/universal/apps/__NAME__/src-tauri/tauri.conf.json +2 -1
- package/templates/pieces/webext/universal/apps/__NAME__/package.json.partial +1 -0
- package/templates/pieces/webext/universal/apps/__NAME__/vite.config.webext.ts +6 -2
- package/types/bin/multiplatformOne.d.ts.map +1 -1
- package/types/commands/addApp.d.ts +20 -0
- package/types/commands/addApp.d.ts.map +1 -0
- package/types/commands/adoptApp.d.ts +4 -3
- package/types/commands/adoptApp.d.ts.map +1 -1
- package/types/commands/doctypeTypes.d.ts +40 -0
- package/types/commands/doctypeTypes.d.ts.map +1 -0
- package/types/commands/init.d.ts +30 -13
- package/types/commands/init.d.ts.map +1 -1
- package/types/commands/merge.d.ts +54 -0
- package/types/commands/merge.d.ts.map +1 -0
- package/types/commands/pieces.d.ts +54 -0
- package/types/commands/pieces.d.ts.map +1 -0
- package/types/commands/provenance.d.ts +52 -0
- package/types/commands/provenance.d.ts.map +1 -0
- package/types/commands/scaffold.d.ts +43 -0
- package/types/commands/scaffold.d.ts.map +1 -0
- package/types/commands/updateApp.d.ts +19 -27
- package/types/commands/updateApp.d.ts.map +1 -1
- package/types/source.d.ts +35 -0
- package/types/source.d.ts.map +1 -0
- package/lib/commands/initApp.mjs +0 -431
- package/lib/generateVscode.mjs +0 -184
- package/scripts/clone.sh +0 -17
- package/scripts/update.sh +0 -46
- package/src/commands/initApp.spec.ts +0 -720
- package/src/commands/initApp.ts +0 -706
- package/src/generateVscode.ts +0 -238
- package/templates/app/AGENTS.md +0 -142
- package/templates/app/README.md +0 -62
- package/templates/app/apps/__NAME__/index.html +0 -36
- package/templates/app/apps/__NAME__/package.json +0 -31
- package/templates/app/apps/__NAME__/public/fonts/inter-400.woff2 +0 -0
- package/templates/app/apps/__NAME__/public/fonts/inter-700.woff2 +0 -0
- package/templates/app/apps/__NAME__/src/App.tsx +0 -9
- package/templates/app/apps/__NAME__/src/main.tsx +0 -14
- package/templates/app/apps/__NAME__/tsconfig.json +0 -8
- package/templates/app/apps/__NAME__/vite.config.ts +0 -17
- package/templates/app/features/__NAME__/package.json +0 -34
- package/templates/app/features/__NAME__/src/HomeScreen.tsx +0 -70
- package/templates/app/features/__NAME__/src/index.ts +0 -1
- package/templates/app/features/__NAME__/tsconfig.json +0 -5
- package/templates/app/gitignore +0 -9
- package/templates/app/npmrc +0 -2
- package/templates/app/oxlintrc.json +0 -39
- package/templates/app/package.json +0 -38
- package/templates/app/packages/config/config.json +0 -4
- package/templates/app/packages/config/index.ts +0 -5
- package/templates/app/packages/config/package.json +0 -17
- package/templates/app/packages/config/tsconfig.json +0 -5
- package/templates/app/pnpm-workspace.yaml +0 -4
- package/templates/app/tsconfig.base.json +0 -16
- package/templates/universal/AGENTS.md +0 -142
- package/templates/universal/README.md +0 -97
- package/templates/universal/apps/__NAME__/app.config.js +0 -31
- package/templates/universal/apps/__NAME__/config/tamagui.config.ts +0 -4
- package/templates/universal/apps/__NAME__/package.json +0 -62
- package/templates/universal/apps/__NAME__/polyfills.native.ts +0 -23
- package/templates/universal/apps/__NAME__/polyfills.ts +0 -3
- package/templates/universal/apps/__NAME__/public/fonts/inter-400.woff2 +0 -0
- package/templates/universal/apps/__NAME__/public/fonts/inter-700.woff2 +0 -0
- package/templates/universal/apps/__NAME__/routes/_layout.tsx +0 -22
- package/templates/universal/apps/__NAME__/routes/_layout.web.tsx +0 -21
- package/templates/universal/apps/__NAME__/routes/about.tsx +0 -3
- package/templates/universal/apps/__NAME__/routes/index.tsx +0 -3
- package/templates/universal/apps/__NAME__/src/app.ts +0 -10
- package/templates/universal/apps/__NAME__/tamagui.css +0 -3
- package/templates/universal/apps/__NAME__/tsconfig.json +0 -8
- package/templates/universal/apps/__NAME__/types.d.ts +0 -9
- package/templates/universal/apps/__NAME__/vite.config.ts +0 -20
- package/templates/universal/features/__NAME__/package.json +0 -35
- package/templates/universal/features/__NAME__/src/AboutScreen.tsx +0 -27
- package/templates/universal/features/__NAME__/src/AppLayout.tsx +0 -50
- package/templates/universal/features/__NAME__/src/ContactForm.spec.tsx +0 -22
- package/templates/universal/features/__NAME__/src/ContactForm.tsx +0 -55
- package/templates/universal/features/__NAME__/src/HomeScreen.tsx +0 -40
- package/templates/universal/features/__NAME__/src/index.ts +0 -4
- package/templates/universal/features/__NAME__/src/screens.spec.ts +0 -12
- package/templates/universal/features/__NAME__/tsconfig.json +0 -5
- package/templates/universal/features/__NAME__/vitest.config.mjs +0 -7
- package/templates/universal/gitignore +0 -14
- package/templates/universal/npmrc +0 -3
- package/templates/universal/oxlintrc.json +0 -39
- package/templates/universal/package.json +0 -51
- package/templates/universal/packages/config/config.json +0 -4
- package/templates/universal/packages/config/index.ts +0 -5
- package/templates/universal/packages/config/package.json +0 -17
- package/templates/universal/packages/config/tsconfig.json +0 -5
- package/templates/universal/packages/i18n/en/common.json +0 -30
- package/templates/universal/packages/i18n/es/common.json +0 -30
- package/templates/universal/packages/i18n/i18n.d.ts +0 -16
- package/templates/universal/packages/i18n/index.ts +0 -26
- package/templates/universal/packages/i18n/package.json +0 -20
- package/templates/universal/packages/i18n/resources.ts +0 -13
- package/templates/universal/packages/i18n/tsconfig.json +0 -5
- package/templates/universal/packages/themes/accent.ts +0 -32
- package/templates/universal/packages/themes/base.ts +0 -50
- package/templates/universal/packages/themes/fonts.ts +0 -8
- package/templates/universal/packages/themes/index.ts +0 -33
- package/templates/universal/packages/themes/package.json +0 -23
- package/templates/universal/packages/themes/tamagui.config.ts +0 -14
- package/templates/universal/packages/themes/tsconfig.json +0 -5
- package/templates/universal/pnpm-workspace.yaml +0 -4
- package/templates/universal/tsconfig.base.json +0 -21
- package/templates/universal/types/env.d.ts +0 -9
- package/types/commands/initApp.d.ts +0 -114
- package/types/commands/initApp.d.ts.map +0 -1
- package/types/generateVscode.d.ts +0 -7
- package/types/generateVscode.d.ts.map +0 -1
|
@@ -1,573 +1,256 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { PROVENANCE_FILE, cliVersion, mpoRange, packageJsonName, readProvenance, writeProvenance } from "./provenance.mjs";
|
|
2
|
+
import { isGitUrl, monorepoCheckout, monorepoUrl, resolveSource } from "../source.mjs";
|
|
3
|
+
import { finishMerge, git, gitOut, landTree, mergeScaffolds, projectRootOf, requireCleanTree } from "./merge.mjs";
|
|
4
|
+
import { archiveSubtree, scaffoldToTemp } from "./scaffold.mjs";
|
|
5
|
+
import { scaffoldPieces, withSplitPieces } from "./pieces.mjs";
|
|
4
6
|
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
-
import
|
|
7
|
+
import { basename, join } from "node:path";
|
|
6
8
|
import { tmpdir } from "node:os";
|
|
7
9
|
|
|
8
10
|
//#region src/commands/updateApp.ts
|
|
9
11
|
/**
|
|
10
|
-
* `mpo update
|
|
11
|
-
*
|
|
12
|
-
* your changes on top), rebuilt with a TRUE three-way merge:
|
|
12
|
+
* `mpo update`: bring the monorepo's changes into a project as a three-way
|
|
13
|
+
* merge that is NEVER committed.
|
|
13
14
|
*
|
|
14
|
-
* base
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* ours
|
|
18
|
-
* theirs=
|
|
15
|
+
* base = the scaffold the project started from: the monorepo at the
|
|
16
|
+
* commit recorded in .mpo.json, pruned with the recorded apps
|
|
17
|
+
* and services
|
|
18
|
+
* ours = the project's HEAD (every commit the project made)
|
|
19
|
+
* theirs = the same scaffold at the commit the running CLI was built from
|
|
20
|
+
* (or --source/--checkout)
|
|
19
21
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* Every product app is a renamed apps/one on both sides, so a change to
|
|
23
|
+
* the One app lands in each of them. The merged tree goes into the index
|
|
24
|
+
* and worktree; the commit line is printed; a dirty tree is refused up
|
|
25
|
+
* front. Adopted projects (`mpo adopt`) merge their piece fragments the
|
|
26
|
+
* same way; projects from the retired template scaffolder reconcile from an
|
|
27
|
+
* empty base once and are clone-derived afterwards.
|
|
25
28
|
*/
|
|
26
|
-
|
|
27
|
-
return
|
|
28
|
-
cwd: projectDir,
|
|
29
|
-
env: env ? {
|
|
30
|
-
...process.env,
|
|
31
|
-
...env
|
|
32
|
-
} : void 0
|
|
33
|
-
});
|
|
29
|
+
function isSha(ref) {
|
|
30
|
+
return /^[0-9a-f]{40}$/.test(ref);
|
|
34
31
|
}
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
/** The commit a source spec names, when it can be known without scaffolding. */
|
|
33
|
+
async function resolveSha(spec) {
|
|
34
|
+
if (isSha(spec.ref)) return spec.ref;
|
|
35
|
+
if (isGitUrl(spec.source)) return void 0;
|
|
36
|
+
return gitOut(spec.source, [
|
|
37
|
+
"rev-parse",
|
|
38
|
+
"--verify",
|
|
39
|
+
`${spec.ref}^{commit}`
|
|
40
|
+
]).catch(() => void 0);
|
|
37
41
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
};
|
|
42
|
+
async function dropStalePnpmfileChecksum(projectDir) {
|
|
43
|
+
const lockfile = join(projectDir, "pnpm-lock.yaml");
|
|
44
|
+
if (existsSync(join(projectDir, ".pnpmfile.cjs")) || !existsSync(lockfile)) return;
|
|
45
|
+
const lock = readFileSync(lockfile, "utf-8");
|
|
46
|
+
const stripped = lock.replace(/^pnpmfileChecksum: .*\n\n?/m, "");
|
|
47
|
+
if (stripped === lock) return;
|
|
48
|
+
writeFileSync(lockfile, stripped);
|
|
46
49
|
await git(projectDir, [
|
|
47
50
|
"add",
|
|
48
|
-
"-A",
|
|
49
|
-
"--force",
|
|
50
51
|
"--",
|
|
51
|
-
"."
|
|
52
|
-
], env);
|
|
53
|
-
const tree = await gitOut(projectDir, ["write-tree"], env);
|
|
54
|
-
const commit = await gitOut(projectDir, parent ? [
|
|
55
|
-
"commit-tree",
|
|
56
|
-
tree,
|
|
57
|
-
"-p",
|
|
58
|
-
parent,
|
|
59
|
-
"-m",
|
|
60
|
-
message
|
|
61
|
-
] : [
|
|
62
|
-
"commit-tree",
|
|
63
|
-
tree,
|
|
64
|
-
"-m",
|
|
65
|
-
message
|
|
52
|
+
"pnpm-lock.yaml"
|
|
66
53
|
]);
|
|
67
|
-
rmSync(indexFile, { force: true });
|
|
68
|
-
return commit;
|
|
69
54
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
* the requested version matches the running CLI (or dlx fails). */
|
|
73
|
-
async function scaffoldBaseline(version, name, template, mpoVersion, pieces) {
|
|
74
|
-
const parent = mkdtempSync(join(tmpdir(), "mpo-update-"));
|
|
75
|
-
const cleanup = () => rmSync(parent, {
|
|
76
|
-
recursive: true,
|
|
77
|
-
force: true
|
|
78
|
-
});
|
|
79
|
-
const current = cliVersion();
|
|
80
|
-
const templateFlag = template === "none" ? ["--pieces-only"] : template === "app" ? ["--web"] : ["--universal"];
|
|
81
|
-
const [major = 0, minor = 0] = version.split(".").map(Number);
|
|
82
|
-
const scaffoldCapable = template === "none" ? major > 6 || major === 6 && minor >= 6 : major > 6 || major === 6 && minor >= 3;
|
|
83
|
-
const piecesCapable = major > 6 || major === 6 && minor >= 5;
|
|
84
|
-
const pieceFlags = piecesCapable ? pieces.map((piece) => `--${piece}`) : [];
|
|
85
|
-
if (pieces.length && scaffoldCapable && !piecesCapable && version !== current) console.warn(`⚠️ @multiplatform.one/cli@${version} predates composable pieces (6.5.0); the merge base is scaffolded without them.`);
|
|
86
|
-
if (!scaffoldCapable) {
|
|
87
|
-
if (version !== current) console.warn(template === "none" ? `⚠️ @multiplatform.one/cli@${version} predates pieces-only baselines (6.6.0); using the current CLI's piece templates as the merge base (the merge degrades to two-way: template changes since that version won't land).` : `⚠️ @multiplatform.one/cli@${version} predates the consumer scaffolder (6.3.0); using the current CLI's template as the merge base.`);
|
|
88
|
-
} else if (version !== current) {
|
|
89
|
-
for (let attempt = 1; attempt <= 2; attempt++) try {
|
|
90
|
-
await spawn("pnpm", [
|
|
91
|
-
"--package",
|
|
92
|
-
`@multiplatform.one/cli@${version}`,
|
|
93
|
-
"dlx",
|
|
94
|
-
"mpo",
|
|
95
|
-
"init",
|
|
96
|
-
name,
|
|
97
|
-
"--yes",
|
|
98
|
-
"--skip-install",
|
|
99
|
-
"--mpo-version",
|
|
100
|
-
mpoVersion,
|
|
101
|
-
...templateFlag,
|
|
102
|
-
...pieceFlags
|
|
103
|
-
], {
|
|
104
|
-
cwd: parent,
|
|
105
|
-
stdio: "inherit"
|
|
106
|
-
});
|
|
107
|
-
const dir = join(parent, name);
|
|
108
|
-
if (existsSync(join(dir, template === "none" ? ".mpo.json" : "package.json"))) return {
|
|
109
|
-
dir,
|
|
110
|
-
cleanup
|
|
111
|
-
};
|
|
112
|
-
console.warn(`⚠️ dlx scaffold for @multiplatform.one/cli@${version} produced no project`);
|
|
113
|
-
break;
|
|
114
|
-
} catch {
|
|
115
|
-
rmSync(join(parent, name), {
|
|
116
|
-
recursive: true,
|
|
117
|
-
force: true
|
|
118
|
-
});
|
|
119
|
-
if (attempt === 2) console.warn(`⚠️ could not scaffold baseline with @multiplatform.one/cli@${version} (network?);`);
|
|
120
|
-
}
|
|
121
|
-
console.warn(" falling back to the current CLI's template as the merge base.");
|
|
122
|
-
}
|
|
123
|
-
const previousCwd = process.cwd();
|
|
124
|
-
process.chdir(parent);
|
|
125
|
-
try {
|
|
126
|
-
await initApp(name, {
|
|
127
|
-
skipInstall: true,
|
|
128
|
-
skipGit: true,
|
|
129
|
-
yes: true,
|
|
130
|
-
template,
|
|
131
|
-
version: mpoVersion,
|
|
132
|
-
pieces
|
|
133
|
-
});
|
|
134
|
-
} finally {
|
|
135
|
-
process.chdir(previousCwd);
|
|
136
|
-
}
|
|
55
|
+
function tempDir() {
|
|
56
|
+
const dir = mkdtempSync(join(tmpdir(), "mpo-update-"));
|
|
137
57
|
return {
|
|
138
|
-
dir
|
|
139
|
-
cleanup
|
|
58
|
+
dir,
|
|
59
|
+
cleanup: () => rmSync(dir, {
|
|
60
|
+
recursive: true,
|
|
61
|
+
force: true
|
|
62
|
+
})
|
|
140
63
|
};
|
|
141
64
|
}
|
|
142
|
-
/**
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
"ls-tree",
|
|
152
|
-
"-r",
|
|
153
|
-
"--name-only",
|
|
154
|
-
tree,
|
|
155
|
-
"--",
|
|
156
|
-
pin
|
|
157
|
-
], env).then((out) => out.split("\n").filter(Boolean)).catch(() => []);
|
|
158
|
-
for (const path of inTree) await git(projectDir, [
|
|
159
|
-
"update-index",
|
|
160
|
-
"--force-remove",
|
|
161
|
-
"--",
|
|
162
|
-
path
|
|
163
|
-
], env).catch(() => {});
|
|
164
|
-
const headEntries = await gitOut(projectDir, [
|
|
165
|
-
"ls-tree",
|
|
166
|
-
"-r",
|
|
167
|
-
headCommit,
|
|
168
|
-
"--",
|
|
169
|
-
pin
|
|
170
|
-
], env).catch(() => "");
|
|
171
|
-
for (const line of headEntries.split("\n").filter(Boolean)) {
|
|
172
|
-
const match = line.match(/^(\d+) \S+ ([0-9a-f]+)\t(.+)$/);
|
|
173
|
-
if (!match) continue;
|
|
174
|
-
await git(projectDir, [
|
|
175
|
-
"update-index",
|
|
176
|
-
"--add",
|
|
177
|
-
"--cacheinfo",
|
|
178
|
-
`${match[1]},${match[2]},${match[3]}`
|
|
179
|
-
], env).catch(() => {});
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
const pinned = await gitOut(projectDir, ["write-tree"], env);
|
|
183
|
-
rmSync(indexFile, { force: true });
|
|
184
|
-
return pinned;
|
|
185
|
-
}
|
|
186
|
-
const UNRESOLVED = Symbol("unresolved");
|
|
187
|
-
/** Deleted-on-one-side sentinel (distinct from a key that never existed). */
|
|
188
|
-
const ABSENT = Symbol("absent");
|
|
189
|
-
const DEP_MAP_KEYS = new Set([
|
|
190
|
-
"dependencies",
|
|
191
|
-
"devDependencies",
|
|
192
|
-
"peerDependencies",
|
|
193
|
-
"optionalDependencies",
|
|
194
|
-
"overrides",
|
|
195
|
-
"resolutions"
|
|
196
|
-
]);
|
|
197
|
-
/** Minimum concrete version of a simple range (`^x.y.z`, `~x.y.z`,
|
|
198
|
-
* `x.y.z`, optional prerelease). Anything fancier is not auto-resolved. */
|
|
199
|
-
function rangeMinimum(range) {
|
|
200
|
-
if (typeof range !== "string") return void 0;
|
|
201
|
-
const match = range.match(/^[\^~]?(\d+)\.(\d+)\.(\d+)(?:-[\w.]+)?$/);
|
|
202
|
-
if (!match) return void 0;
|
|
203
|
-
return [
|
|
204
|
-
Number(match[1]),
|
|
205
|
-
Number(match[2]),
|
|
206
|
-
Number(match[3])
|
|
207
|
-
];
|
|
208
|
-
}
|
|
209
|
-
function isPlainObject(value) {
|
|
210
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
211
|
-
}
|
|
212
|
-
function jsonEqual(a, b) {
|
|
213
|
-
return JSON.stringify(a) === JSON.stringify(b);
|
|
214
|
-
}
|
|
215
|
-
function mergeNode(base, ours, theirs, inDepMap) {
|
|
216
|
-
if (jsonEqual(ours, theirs)) return ours === void 0 ? ABSENT : ours;
|
|
217
|
-
if (jsonEqual(theirs, base)) return ours === void 0 ? ABSENT : ours;
|
|
218
|
-
if (jsonEqual(ours, base)) return theirs === void 0 ? ABSENT : theirs;
|
|
219
|
-
if (isPlainObject(ours) && isPlainObject(theirs)) {
|
|
220
|
-
const baseObject = isPlainObject(base) ? base : {};
|
|
221
|
-
const merged = {};
|
|
222
|
-
const keys = [...Object.keys(ours), ...Object.keys(theirs).filter((key) => !(key in ours))];
|
|
223
|
-
for (const key of keys) {
|
|
224
|
-
const child = mergeNode(baseObject[key], ours[key], theirs[key], inDepMap || DEP_MAP_KEYS.has(key));
|
|
225
|
-
if (child === UNRESOLVED) return UNRESOLVED;
|
|
226
|
-
if (child !== ABSENT) merged[key] = child;
|
|
227
|
-
}
|
|
228
|
-
return merged;
|
|
229
|
-
}
|
|
230
|
-
if (inDepMap) {
|
|
231
|
-
const oursMin = rangeMinimum(ours);
|
|
232
|
-
const theirsMin = rangeMinimum(theirs);
|
|
233
|
-
if (oursMin && theirsMin) {
|
|
234
|
-
for (let i = 0; i < 3; i++) {
|
|
235
|
-
if (oursMin[i] > theirsMin[i]) return ours;
|
|
236
|
-
if (oursMin[i] < theirsMin[i]) return theirs;
|
|
237
|
-
}
|
|
238
|
-
return theirs;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
return UNRESOLVED;
|
|
242
|
-
}
|
|
243
|
-
/** Structural three-way merge of package.json content. Returns the merged
|
|
244
|
-
* object, or undefined when a divergence can't be auto-resolved (the file
|
|
245
|
-
* then keeps its textual conflict markers for manual resolution). */
|
|
246
|
-
function threeWayMergePackageJson(base, ours, theirs) {
|
|
247
|
-
let baseJson;
|
|
248
|
-
let oursJson;
|
|
249
|
-
let theirsJson;
|
|
250
|
-
try {
|
|
251
|
-
baseJson = base === void 0 ? {} : JSON.parse(base);
|
|
252
|
-
oursJson = JSON.parse(ours);
|
|
253
|
-
theirsJson = JSON.parse(theirs);
|
|
254
|
-
} catch {
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
if (!isPlainObject(oursJson) || !isPlainObject(theirsJson)) return void 0;
|
|
258
|
-
const merged = mergeNode(baseJson, oursJson, theirsJson, false);
|
|
259
|
-
if (merged === UNRESOLVED || merged === ABSENT || !isPlainObject(merged)) return void 0;
|
|
260
|
-
return merged;
|
|
261
|
-
}
|
|
262
|
-
/** Replace auto-resolvable conflicted package.json files in `mergedTree`
|
|
263
|
-
* with their structural merge. Pure index plumbing, worktree untouched. */
|
|
264
|
-
async function autoResolveDependencyConflicts(projectDir, mergedTree, conflicts, baseCommit, headCommit, nextCommit) {
|
|
265
|
-
const candidates = conflicts.filter((file) => basename(file) === "package.json");
|
|
266
|
-
if (!candidates.length) return {
|
|
267
|
-
tree: mergedTree,
|
|
268
|
-
conflicts,
|
|
269
|
-
resolved: []
|
|
270
|
-
};
|
|
271
|
-
const scratch = mkdtempSync(join(tmpdir(), "mpo-resolve-"));
|
|
272
|
-
const resolved = [];
|
|
65
|
+
/**
|
|
66
|
+
* templates/pieces as they were at another CLI version: the monorepo tag
|
|
67
|
+
* v<version>, from the CLI's source (or --source). Falls back to the
|
|
68
|
+
* current templates with a warning, which degrades that merge to two-way.
|
|
69
|
+
*/
|
|
70
|
+
async function piecesRootAt(version, options) {
|
|
71
|
+
if (version === cliVersion()) return { cleanup: () => {} };
|
|
72
|
+
const scratch = tempDir();
|
|
73
|
+
const source = options.source ?? monorepoCheckout() ?? "https://git.corp.bitspur.com/multiplatform.one/mpo.git";
|
|
273
74
|
try {
|
|
274
|
-
|
|
275
|
-
const show = (commit) => gitOut(projectDir, ["show", `${commit}:${file}`]).catch(() => void 0);
|
|
276
|
-
const [base, ours, theirs] = await Promise.all([
|
|
277
|
-
show(baseCommit),
|
|
278
|
-
show(headCommit),
|
|
279
|
-
show(nextCommit)
|
|
280
|
-
]);
|
|
281
|
-
if (ours === void 0 || theirs === void 0) continue;
|
|
282
|
-
const merged = threeWayMergePackageJson(base, ours, theirs);
|
|
283
|
-
if (!merged) continue;
|
|
284
|
-
const blobFile = join(scratch, "blob.json");
|
|
285
|
-
writeFileSync(blobFile, `${JSON.stringify(merged, null, 2)}\n`);
|
|
286
|
-
const oid = await gitOut(projectDir, [
|
|
287
|
-
"hash-object",
|
|
288
|
-
"-w",
|
|
289
|
-
"--",
|
|
290
|
-
blobFile
|
|
291
|
-
]);
|
|
292
|
-
resolved.push({
|
|
293
|
-
path: file,
|
|
294
|
-
oid
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
if (!resolved.length) return {
|
|
298
|
-
tree: mergedTree,
|
|
299
|
-
conflicts,
|
|
300
|
-
resolved: []
|
|
301
|
-
};
|
|
302
|
-
const env = { GIT_INDEX_FILE: join(scratch, "index") };
|
|
303
|
-
await git(projectDir, ["read-tree", mergedTree], env);
|
|
304
|
-
for (const entry of resolved) await git(projectDir, [
|
|
305
|
-
"update-index",
|
|
306
|
-
"--add",
|
|
307
|
-
"--cacheinfo",
|
|
308
|
-
`100644,${entry.oid},${entry.path}`
|
|
309
|
-
], env);
|
|
310
|
-
const tree = await gitOut(projectDir, ["write-tree"], env);
|
|
311
|
-
const resolvedPaths = new Set(resolved.map((entry) => entry.path));
|
|
75
|
+
await archiveSubtree(source, `v${version}`, "public/cli/templates/pieces", scratch.dir);
|
|
312
76
|
return {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
resolved: [...resolvedPaths]
|
|
77
|
+
root: join(scratch.dir, "public/cli/templates/pieces"),
|
|
78
|
+
cleanup: scratch.cleanup
|
|
316
79
|
};
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
});
|
|
80
|
+
} catch {
|
|
81
|
+
scratch.cleanup();
|
|
82
|
+
console.warn(`⚠️ could not read templates/pieces at v${version} from ${source}; using the current pieces as the merge base (changes since that version won't land).`);
|
|
83
|
+
return { cleanup: () => {} };
|
|
322
84
|
}
|
|
323
85
|
}
|
|
324
|
-
|
|
325
|
-
* Pre-provenance bootstrap (`mpo update --assume-version <cliVersion>`):
|
|
326
|
-
* projects scaffolded by CLIs older than the provenance file (≤6.3.0) have
|
|
327
|
-
* no .mpo.json. The user asserts which CLI version (and template) produced
|
|
328
|
-
* the project; provenance is written and committed, then the normal update
|
|
329
|
-
* proceeds against that baseline.
|
|
330
|
-
*/
|
|
331
|
-
async function bootstrapProvenance(projectDir, options) {
|
|
332
|
-
const version = options.assumeVersion;
|
|
333
|
-
if (!/^\d+\.\d+\.\d+(?:-[\w.]+)?$/u.test(version)) throw new Error(`--assume-version must be an exact CLI version (e.g. 6.3.0), got "${version}"`);
|
|
334
|
-
const rawName = packageJsonName(projectDir);
|
|
335
|
-
if (!rawName) throw new Error(`Could not derive the project name from package.json — create ${PROVENANCE_FILE} by hand with { template, cliVersion, name } instead`);
|
|
336
|
-
const provenance = {
|
|
337
|
-
template: options.assumeTemplate ?? "universal",
|
|
338
|
-
cliVersion: version,
|
|
339
|
-
name: validateName(rawName),
|
|
340
|
-
mpoVersion: `^${version}`,
|
|
341
|
-
pieces: []
|
|
342
|
-
};
|
|
343
|
-
console.log(`\nBootstrapping provenance: assuming this project was scaffolded by cli@${version} (${provenance.template} template).`);
|
|
344
|
-
writeProvenance(projectDir, provenance);
|
|
345
|
-
await git(projectDir, [
|
|
346
|
-
"add",
|
|
347
|
-
"--",
|
|
348
|
-
PROVENANCE_FILE
|
|
349
|
-
]);
|
|
350
|
-
await git(projectDir, [
|
|
351
|
-
"commit",
|
|
352
|
-
"--quiet",
|
|
353
|
-
"-m",
|
|
354
|
-
`chore: record assumed mpo scaffold provenance (cli@${version})`
|
|
355
|
-
]).catch(() => {
|
|
356
|
-
console.warn("⚠️ git commit failed (missing git identity?) — provenance left staged.");
|
|
357
|
-
});
|
|
358
|
-
return provenance;
|
|
359
|
-
}
|
|
360
|
-
async function updateApp(options = {}) {
|
|
361
|
-
const projectDir = await gitOut(process.cwd(), ["rev-parse", "--show-toplevel"]).catch(() => {
|
|
362
|
-
throw new Error("mpo update must run inside a git repository");
|
|
363
|
-
});
|
|
364
|
-
await git(projectDir, ["diff", "--quiet"]).catch(() => {
|
|
365
|
-
throw new Error("mpo update requires a clean working tree (unstaged changes present)");
|
|
366
|
-
});
|
|
367
|
-
await git(projectDir, [
|
|
368
|
-
"diff",
|
|
369
|
-
"--cached",
|
|
370
|
-
"--quiet"
|
|
371
|
-
]).catch(() => {
|
|
372
|
-
throw new Error("mpo update requires a clean working tree (staged changes present)");
|
|
373
|
-
});
|
|
374
|
-
let provenance = readProvenance(projectDir);
|
|
375
|
-
if (options.assumeVersion) {
|
|
376
|
-
if (provenance) throw new Error(`--assume-version is for projects without ${PROVENANCE_FILE} — this project already has provenance (cli@${provenance.cliVersion})`);
|
|
377
|
-
provenance = await bootstrapProvenance(projectDir, options);
|
|
378
|
-
}
|
|
379
|
-
if (!provenance) throw new Error(`${PROVENANCE_FILE} not found — this project predates scaffold provenance.\n - scaffolded by an old CLI (pre-6.3)? bootstrap it:
|
|
380
|
-
mpo update --assume-version <cliVersion> [--template universal|app]
|
|
381
|
-
- never scaffolded (hand-adopted pieces)? bring it under management:
|
|
382
|
-
mpo adopt --<piece> [...]`);
|
|
86
|
+
async function updateAdopted(projectDir, provenance, options) {
|
|
383
87
|
const currentVersion = cliVersion() ?? "0.0.0";
|
|
384
|
-
const
|
|
385
|
-
const
|
|
386
|
-
const firstReconcile =
|
|
387
|
-
console.log(`\nmpo update: ${provenance.cliVersion} → ${currentVersion}`);
|
|
388
|
-
console.log(`
|
|
389
|
-
|
|
390
|
-
console.log(` project: ${provenance.name}\n`);
|
|
88
|
+
const targetRange = mpoRange(options.version, currentVersion);
|
|
89
|
+
const name = provenance.name;
|
|
90
|
+
const firstReconcile = provenance.reconciled === false;
|
|
91
|
+
console.log(`\nmpo update (adopted): cli ${provenance.cliVersion} → ${currentVersion}`);
|
|
92
|
+
console.log(` pieces ${provenance.pieces.join(", ")}`);
|
|
93
|
+
console.log(` project ${name}\n`);
|
|
391
94
|
if (firstReconcile) {
|
|
392
95
|
console.log("⚠️ FIRST UPDATE AFTER ADOPTION: piece files are reconciled against the");
|
|
393
96
|
console.log(" piece templates from an empty merge base. Diffs and conflicts on");
|
|
394
97
|
console.log(" hand-copied piece files are EXPECTED — resolve the markers and");
|
|
395
98
|
console.log(" commit. Later updates merge three-way and stay quiet.\n");
|
|
396
99
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
base = {
|
|
402
|
-
dir,
|
|
403
|
-
cleanup: () => rmSync(dir, {
|
|
404
|
-
recursive: true,
|
|
405
|
-
force: true
|
|
406
|
-
})
|
|
407
|
-
};
|
|
408
|
-
} else {
|
|
409
|
-
console.log(`Scaffolding merge base (cli@${provenance.cliVersion})...`);
|
|
410
|
-
base = await scaffoldBaseline(provenance.cliVersion, provenance.name, provenance.template, provenance.mpoVersion, provenance.pieces);
|
|
411
|
-
}
|
|
412
|
-
console.log(`Scaffolding update target (cli@${currentVersion})...`);
|
|
413
|
-
const next = await scaffoldBaseline(currentVersion, provenance.name, provenance.template, targetMpoRange, provenance.pieces);
|
|
414
|
-
rmSync(join(base.dir, ".mpo.json"), { force: true });
|
|
415
|
-
rmSync(join(next.dir, ".mpo.json"), { force: true });
|
|
100
|
+
const base = tempDir();
|
|
101
|
+
const theirs = tempDir();
|
|
102
|
+
let oldPieces = { cleanup: () => {} };
|
|
103
|
+
let pieces = provenance.pieces;
|
|
416
104
|
try {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
`--merge-base=${baseCommit}`,
|
|
428
|
-
head,
|
|
429
|
-
nextCommit
|
|
430
|
-
])).split("\n")[0].trim();
|
|
431
|
-
} catch (err) {
|
|
432
|
-
const lines = (err.stdout ?? "").split("\n");
|
|
433
|
-
if (!lines[0]?.trim()) throw err;
|
|
434
|
-
mergedTree = lines[0].trim();
|
|
435
|
-
const blank = lines.indexOf("", 1);
|
|
436
|
-
conflicts = lines.slice(1, blank === -1 ? void 0 : blank).map((line) => line.trim()).filter(Boolean);
|
|
105
|
+
if (!firstReconcile) {
|
|
106
|
+
oldPieces = await piecesRootAt(provenance.cliVersion, options);
|
|
107
|
+
scaffoldPieces(base.dir, {
|
|
108
|
+
name,
|
|
109
|
+
mpoVersion: provenance.mpoVersion ?? `^${provenance.cliVersion}`,
|
|
110
|
+
pieces: provenance.pieces,
|
|
111
|
+
root: oldPieces.root
|
|
112
|
+
});
|
|
113
|
+
pieces = withSplitPieces(provenance.pieces, oldPieces.root);
|
|
114
|
+
if (pieces !== provenance.pieces) console.log(" frappe-bench added: the frappe piece no longer carries the dev bench\n");
|
|
437
115
|
}
|
|
438
|
-
|
|
439
|
-
|
|
116
|
+
scaffoldPieces(theirs.dir, {
|
|
117
|
+
name,
|
|
118
|
+
mpoVersion: targetRange,
|
|
119
|
+
pieces
|
|
120
|
+
});
|
|
121
|
+
const outcome = await mergeScaffolds({
|
|
122
|
+
projectDir,
|
|
123
|
+
baseDir: firstReconcile ? void 0 : base.dir,
|
|
124
|
+
theirsDir: theirs.dir,
|
|
125
|
+
baseLabel: `mpo pieces ${provenance.cliVersion}`,
|
|
126
|
+
theirsLabel: `mpo pieces ${currentVersion}`
|
|
127
|
+
});
|
|
128
|
+
const next = {
|
|
129
|
+
...provenance,
|
|
440
130
|
cliVersion: currentVersion,
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
131
|
+
mpoVersion: targetRange,
|
|
132
|
+
pieces,
|
|
133
|
+
reconciled: void 0
|
|
444
134
|
};
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
if (!pattern || pattern.startsWith("#")) continue;
|
|
450
|
-
const normalized = pattern.replace(/\/\*{1,2}$/, "");
|
|
451
|
-
if (/[*?[\]]/.test(normalized)) throw new Error(`.updateignore: "${pattern}" looks like a glob, but entries are exact files or directory prefixes (a glob here would silently pin nothing). List the file or directory instead.`);
|
|
452
|
-
pins.push(normalized.replace(/\/+$/, ""));
|
|
453
|
-
}
|
|
454
|
-
mergedTree = await pinPathsToHead(projectDir, mergedTree, head, pins);
|
|
455
|
-
conflicts = conflicts.filter((file) => !pins.some((pin) => file === pin || file.startsWith(`${pin}/`)));
|
|
456
|
-
const autoResolved = await autoResolveDependencyConflicts(projectDir, mergedTree, conflicts, baseCommit, head, nextCommit);
|
|
457
|
-
mergedTree = autoResolved.tree;
|
|
458
|
-
conflicts = autoResolved.conflicts;
|
|
459
|
-
for (const file of autoResolved.resolved) console.log(` auto-resolved: ${file} (dependency ranges)`);
|
|
460
|
-
const headTree = await gitOut(projectDir, ["rev-parse", `${head}^{tree}`]);
|
|
461
|
-
if (!conflicts.length && mergedTree === headTree) {
|
|
462
|
-
if (provenance.cliVersion !== finalProvenance.cliVersion || provenance.mpoVersion !== finalProvenance.mpoVersion || provenance.reconciled === false) {
|
|
463
|
-
writeProvenance(projectDir, finalProvenance);
|
|
464
|
-
await git(projectDir, [
|
|
465
|
-
"add",
|
|
466
|
-
"--",
|
|
467
|
-
".mpo.json"
|
|
468
|
-
]);
|
|
469
|
-
await git(projectDir, [
|
|
470
|
-
"commit",
|
|
471
|
-
"--quiet",
|
|
472
|
-
"-m",
|
|
473
|
-
`chore: record mpo template provenance ${currentVersion}`
|
|
474
|
-
]);
|
|
475
|
-
console.log("\n✅ already up to date (provenance refreshed)");
|
|
135
|
+
if (!outcome.changed && !outcome.conflicts.length) {
|
|
136
|
+
if (provenance.cliVersion !== currentVersion || provenance.mpoVersion !== targetRange || pieces !== provenance.pieces || firstReconcile) {
|
|
137
|
+
writeProvenance(projectDir, next);
|
|
138
|
+
console.log(`\n✅ already up to date; ${PROVENANCE_FILE} refreshed — commit it`);
|
|
476
139
|
} else console.log("\n✅ already up to date");
|
|
477
140
|
return;
|
|
478
141
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
"--quiet"
|
|
516
|
-
]);
|
|
517
|
-
cleanMergeMessage = message;
|
|
518
|
-
} else {
|
|
519
|
-
await git(projectDir, [
|
|
520
|
-
"read-tree",
|
|
521
|
-
"-u",
|
|
522
|
-
"--reset",
|
|
523
|
-
mergedTree
|
|
524
|
-
]);
|
|
525
|
-
writeProvenance(projectDir, finalProvenance);
|
|
526
|
-
console.log("\n⚠️ merge conflicts — resolve the markers, then commit:");
|
|
527
|
-
for (const file of conflicts) console.log(` ${file}`);
|
|
528
|
-
console.log(`\n git add -A && git commit -m "chore: mpo update ${provenance.cliVersion} → ${currentVersion}"`);
|
|
142
|
+
await landTree(projectDir, outcome.mergedTree);
|
|
143
|
+
writeProvenance(projectDir, next);
|
|
144
|
+
await finishMerge(projectDir, outcome, `chore: mpo update ${provenance.cliVersion} → ${currentVersion}`, options);
|
|
145
|
+
} finally {
|
|
146
|
+
base.cleanup();
|
|
147
|
+
theirs.cleanup();
|
|
148
|
+
oldPieces.cleanup();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async function updateClone(projectDir, provenance, options) {
|
|
152
|
+
const currentVersion = cliVersion() ?? "0.0.0";
|
|
153
|
+
const recorded = provenance.source;
|
|
154
|
+
const templateEra = !recorded;
|
|
155
|
+
const apps = provenance.apps;
|
|
156
|
+
if (!apps.length) throw new Error(`${PROVENANCE_FILE} lists no product apps — nothing to update`);
|
|
157
|
+
const nextSpec = resolveSource({
|
|
158
|
+
source: options.source,
|
|
159
|
+
checkout: options.checkout
|
|
160
|
+
});
|
|
161
|
+
if (nextSpec.warning) console.warn(`⚠️ ${nextSpec.warning}`);
|
|
162
|
+
const baseSource = options.source ?? recorded?.url ?? nextSpec.source;
|
|
163
|
+
const targetRange = options.version ? mpoRange(options.version) : void 0;
|
|
164
|
+
console.log(`\nmpo update: ${recorded ? recorded.sha.slice(0, 12) : "(template-era project)"} → ${nextSpec.source} @ ${nextSpec.ref}`);
|
|
165
|
+
console.log(` apps ${apps.join(", ")}`);
|
|
166
|
+
console.log(` services ${provenance.services.length ? provenance.services.join(", ") : "(none)"}`);
|
|
167
|
+
if (templateEra) {
|
|
168
|
+
console.log("\n⚠️ This project came from the retired template scaffolder, so there is no");
|
|
169
|
+
console.log(" scaffold to use as a merge base. The monorepo's One app is merged in");
|
|
170
|
+
console.log(" from an empty base once: expect additions and conflicts on shared files.");
|
|
171
|
+
console.log(" Pin what is yours in .updateignore first if needed. After this update the");
|
|
172
|
+
console.log(` project is clone-derived and later updates merge three-way.\n`);
|
|
173
|
+
}
|
|
174
|
+
if (recorded && !options.version) {
|
|
175
|
+
if (await resolveSha(nextSpec) === recorded.sha) {
|
|
176
|
+
console.log("\n✅ already up to date");
|
|
177
|
+
return;
|
|
529
178
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
"pnpm-lock.yaml"
|
|
548
|
-
]);
|
|
549
|
-
await git(projectDir, [
|
|
550
|
-
"commit",
|
|
551
|
-
"--amend",
|
|
552
|
-
"--no-edit",
|
|
553
|
-
"--quiet"
|
|
554
|
-
]);
|
|
555
|
-
}
|
|
556
|
-
}
|
|
179
|
+
}
|
|
180
|
+
const name = packageJsonName(projectDir) ?? basename(projectDir);
|
|
181
|
+
const theirs = await scaffoldToTemp({
|
|
182
|
+
source: nextSpec.source,
|
|
183
|
+
ref: nextSpec.ref,
|
|
184
|
+
apps,
|
|
185
|
+
services: provenance.services,
|
|
186
|
+
mpoVersion: targetRange,
|
|
187
|
+
name,
|
|
188
|
+
verbose: options.verbose
|
|
189
|
+
});
|
|
190
|
+
let base;
|
|
191
|
+
try {
|
|
192
|
+
const nextSha = theirs.provenance.source.sha;
|
|
193
|
+
if (recorded && nextSha === recorded.sha && !options.version) {
|
|
194
|
+
console.log("\n✅ already up to date");
|
|
195
|
+
return;
|
|
557
196
|
}
|
|
558
|
-
if (
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
197
|
+
if (recorded) base = await scaffoldToTemp({
|
|
198
|
+
source: baseSource,
|
|
199
|
+
ref: recorded.sha,
|
|
200
|
+
apps,
|
|
201
|
+
services: provenance.services,
|
|
202
|
+
mpoVersion: `^${provenance.cliVersion}`,
|
|
203
|
+
name,
|
|
204
|
+
verbose: options.verbose
|
|
205
|
+
});
|
|
206
|
+
for (const dir of [theirs.dir, base?.dir]) if (dir) rmSync(join(dir, PROVENANCE_FILE), { force: true });
|
|
207
|
+
const outcome = await mergeScaffolds({
|
|
208
|
+
projectDir,
|
|
209
|
+
baseDir: base?.dir,
|
|
210
|
+
theirsDir: theirs.dir,
|
|
211
|
+
baseLabel: recorded ? `mpo scaffold ${recorded.sha}` : "mpo scaffold (empty base)",
|
|
212
|
+
theirsLabel: `mpo scaffold ${nextSha}`
|
|
213
|
+
});
|
|
214
|
+
const next = {
|
|
215
|
+
cliVersion: theirs.provenance.cliVersion || currentVersion,
|
|
216
|
+
source: {
|
|
217
|
+
sha: nextSha,
|
|
218
|
+
url: options.source ?? recorded?.url ?? nextSpec.source
|
|
219
|
+
},
|
|
220
|
+
apps,
|
|
221
|
+
services: provenance.services,
|
|
222
|
+
pieces: []
|
|
223
|
+
};
|
|
224
|
+
const label = `${recorded ? recorded.sha.slice(0, 12) : "template"} → ${nextSha.slice(0, 12)}`;
|
|
225
|
+
if (!outcome.changed && !outcome.conflicts.length) {
|
|
226
|
+
writeProvenance(projectDir, next);
|
|
227
|
+
console.log(`\n✅ already up to date; ${PROVENANCE_FILE} refreshed — commit it`);
|
|
228
|
+
return;
|
|
565
229
|
}
|
|
230
|
+
await landTree(projectDir, outcome.mergedTree);
|
|
231
|
+
await dropStalePnpmfileChecksum(projectDir);
|
|
232
|
+
writeProvenance(projectDir, next);
|
|
233
|
+
await finishMerge(projectDir, outcome, `chore: mpo update ${label}`, options);
|
|
566
234
|
} finally {
|
|
567
|
-
|
|
568
|
-
|
|
235
|
+
theirs.cleanup();
|
|
236
|
+
base?.cleanup();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
async function updateApp(options = {}) {
|
|
240
|
+
const projectDir = await projectRootOf("mpo update");
|
|
241
|
+
await requireCleanTree(projectDir, "mpo update");
|
|
242
|
+
const provenance = readProvenance(projectDir);
|
|
243
|
+
if (!provenance) throw new Error(`${PROVENANCE_FILE} not found — this project has no scaffold provenance.\n - hand-copied piece files from the templates? bring it under management:
|
|
244
|
+
mpo adopt --<piece> [...]
|
|
245
|
+
- scaffolded from the monorepo before provenance existed? write .mpo.json:
|
|
246
|
+
{ "cliVersion": "<cli>", "source": { "sha": "<commit>", "url": "<repo>" },
|
|
247
|
+
"apps": ["<name>"], "services": [], "pieces": [] }`);
|
|
248
|
+
if (provenance.template === "none") {
|
|
249
|
+
await updateAdopted(projectDir, provenance, options);
|
|
250
|
+
return;
|
|
569
251
|
}
|
|
252
|
+
await updateClone(projectDir, provenance, options);
|
|
570
253
|
}
|
|
571
254
|
|
|
572
255
|
//#endregion
|
|
573
|
-
export {
|
|
256
|
+
export { updateApp };
|