@multiplatform.one/cli 6.1.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/README.md +72 -0
- package/bin/createMultiplatformApp.mjs +77 -0
- package/lib/bin/multiplatformOne.mjs +35 -10
- package/lib/commands/e2e.mjs +3 -3
- package/lib/commands/init.mjs +16 -12
- package/lib/commands/initApp.mjs +175 -0
- package/lib/commands/updateApp.mjs +260 -0
- package/lib/generateVscode.mjs +27 -10
- package/package.json +23 -12
- package/src/bin/multiplatformOne.ts +83 -20
- package/src/commands/init.ts +7 -18
- package/src/commands/initApp.spec.ts +210 -0
- package/src/commands/initApp.ts +274 -0
- package/src/commands/updateApp.ts +277 -0
- package/src/generateVscode.ts +31 -9
- package/templates/app/AGENTS.md +142 -0
- package/templates/app/README.md +62 -0
- package/templates/app/apps/__NAME__/index.html +36 -0
- package/templates/app/apps/__NAME__/package.json +30 -0
- 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 +9 -0
- package/templates/app/apps/__NAME__/src/main.tsx +14 -0
- package/templates/app/apps/__NAME__/tsconfig.json +8 -0
- package/templates/app/apps/__NAME__/vite.config.ts +17 -0
- package/templates/app/features/__NAME__/package.json +34 -0
- package/templates/app/features/__NAME__/src/HomeScreen.tsx +70 -0
- package/templates/app/features/__NAME__/src/index.ts +1 -0
- package/templates/app/features/__NAME__/tsconfig.json +5 -0
- package/templates/app/gitignore +8 -0
- package/templates/app/npmrc +2 -0
- package/templates/app/oxlintrc.json +39 -0
- package/templates/app/package.json +20 -0
- package/templates/app/packages/config/config.json +4 -0
- package/templates/app/packages/config/index.ts +5 -0
- package/templates/app/packages/config/package.json +17 -0
- package/templates/app/packages/config/tsconfig.json +5 -0
- package/templates/app/pnpm-workspace.yaml +4 -0
- package/templates/app/tsconfig.base.json +16 -0
- package/templates/universal/AGENTS.md +142 -0
- package/templates/universal/README.md +97 -0
- package/templates/universal/apps/__NAME__/app.config.js +31 -0
- package/templates/universal/apps/__NAME__/config/tamagui.config.ts +4 -0
- package/templates/universal/apps/__NAME__/package.json +61 -0
- package/templates/universal/apps/__NAME__/polyfills.native.ts +23 -0
- package/templates/universal/apps/__NAME__/polyfills.ts +3 -0
- 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 +22 -0
- package/templates/universal/apps/__NAME__/routes/_layout.web.tsx +21 -0
- package/templates/universal/apps/__NAME__/routes/about.tsx +3 -0
- package/templates/universal/apps/__NAME__/routes/index.tsx +3 -0
- package/templates/universal/apps/__NAME__/src/app.ts +10 -0
- package/templates/universal/apps/__NAME__/tamagui.css +3 -0
- package/templates/universal/apps/__NAME__/tsconfig.json +8 -0
- package/templates/universal/apps/__NAME__/types.d.ts +9 -0
- package/templates/universal/apps/__NAME__/vite.config.ts +66 -0
- package/templates/universal/features/__NAME__/package.json +35 -0
- package/templates/universal/features/__NAME__/src/AboutScreen.tsx +27 -0
- package/templates/universal/features/__NAME__/src/AppLayout.tsx +50 -0
- package/templates/universal/features/__NAME__/src/ContactForm.spec.tsx +22 -0
- package/templates/universal/features/__NAME__/src/ContactForm.tsx +55 -0
- package/templates/universal/features/__NAME__/src/HomeScreen.tsx +40 -0
- package/templates/universal/features/__NAME__/src/index.ts +4 -0
- package/templates/universal/features/__NAME__/src/screens.spec.ts +12 -0
- package/templates/universal/features/__NAME__/tsconfig.json +5 -0
- package/templates/universal/features/__NAME__/vitest.config.mjs +7 -0
- package/templates/universal/gitignore +12 -0
- package/templates/universal/npmrc +3 -0
- package/templates/universal/oxlintrc.json +39 -0
- package/templates/universal/package.json +27 -0
- package/templates/universal/packages/config/config.json +4 -0
- package/templates/universal/packages/config/index.ts +5 -0
- package/templates/universal/packages/config/package.json +17 -0
- package/templates/universal/packages/config/tsconfig.json +5 -0
- package/templates/universal/packages/i18n/en/common.json +30 -0
- package/templates/universal/packages/i18n/es/common.json +30 -0
- package/templates/universal/packages/i18n/i18n.d.ts +16 -0
- package/templates/universal/packages/i18n/index.ts +26 -0
- package/templates/universal/packages/i18n/package.json +20 -0
- package/templates/universal/packages/i18n/resources.ts +13 -0
- package/templates/universal/packages/i18n/tsconfig.json +5 -0
- package/templates/universal/packages/themes/accent.ts +32 -0
- package/templates/universal/packages/themes/base.ts +50 -0
- package/templates/universal/packages/themes/fonts.ts +8 -0
- package/templates/universal/packages/themes/index.ts +33 -0
- package/templates/universal/packages/themes/package.json +23 -0
- package/templates/universal/packages/themes/tamagui.config.ts +14 -0
- package/templates/universal/packages/themes/tsconfig.json +5 -0
- package/templates/universal/pnpm-workspace.yaml +4 -0
- package/templates/universal/tsconfig.base.json +21 -0
- package/templates/universal/types/env.d.ts +9 -0
- package/types/bin/multiplatformOne.d.ts +11 -0
- package/types/bin/multiplatformOne.d.ts.map +1 -0
- package/types/commands/e2e.d.ts +14 -0
- package/types/commands/e2e.d.ts.map +1 -0
- package/types/commands/init.d.ts +16 -0
- package/types/commands/init.d.ts.map +1 -0
- package/types/commands/initApp.d.ts +41 -0
- package/types/commands/initApp.d.ts.map +1 -0
- package/types/commands/updateApp.d.ts +24 -0
- package/types/commands/updateApp.d.ts.map +1 -0
- package/types/generateVscode.d.ts +7 -0
- package/types/generateVscode.d.ts.map +1 -0
- package/types/index.d.ts +2 -0
- package/types/index.d.ts.map +1 -0
- package/types/types.d.ts +6 -0
- package/types/types.d.ts.map +1 -0
|
@@ -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/lib/generateVscode.mjs
CHANGED
|
@@ -41,14 +41,30 @@ function launchConfigForApp(app) {
|
|
|
41
41
|
const { name, path: relPath, scripts } = app;
|
|
42
42
|
const configs = [];
|
|
43
43
|
const wf = "${workspaceFolder}";
|
|
44
|
-
if (
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
if (scripts["dev:vscode"]) {
|
|
45
|
+
configs.push({
|
|
46
|
+
name: "vscode dev",
|
|
47
|
+
type: "extensionHost",
|
|
48
|
+
request: "launch",
|
|
49
|
+
args: ["--disable-extensions", `--extensionDevelopmentPath=${wf}/${relPath}/dist-vscode`],
|
|
50
|
+
cwd: `${wf}/${relPath}`,
|
|
51
|
+
outFiles: [`${wf}/${relPath}/dist-vscode/extension/*.js`],
|
|
52
|
+
preLaunchTask: "npm: dev:vscode"
|
|
53
|
+
});
|
|
54
|
+
configs.push({
|
|
55
|
+
name: "vscode dev",
|
|
56
|
+
type: "node-terminal",
|
|
57
|
+
request: "launch",
|
|
58
|
+
cwd: `${wf}/${relPath}`,
|
|
59
|
+
command: "pnpm dev:vscode"
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (scripts["dev:webext"]) configs.push({
|
|
63
|
+
name: "webext dev",
|
|
64
|
+
type: "node-terminal",
|
|
47
65
|
request: "launch",
|
|
48
|
-
args: ["--disable-extensions", `--extensionDevelopmentPath=${wf}/${relPath}`],
|
|
49
66
|
cwd: `${wf}/${relPath}`,
|
|
50
|
-
|
|
51
|
-
preLaunchTask: "npm: dev"
|
|
67
|
+
command: "pnpm dev:webext"
|
|
52
68
|
});
|
|
53
69
|
if (scripts.dev) configs.push({
|
|
54
70
|
name: `${name} dev`,
|
|
@@ -117,12 +133,13 @@ async function generateLaunchJson(root) {
|
|
|
117
133
|
};
|
|
118
134
|
}
|
|
119
135
|
async function generateTasksJson(apps) {
|
|
120
|
-
|
|
136
|
+
const hostApp = apps.find((a) => a.scripts?.["dev:vscode"]);
|
|
137
|
+
if (!hostApp) return null;
|
|
121
138
|
return {
|
|
122
139
|
version: "2.0.0",
|
|
123
140
|
tasks: [{
|
|
124
141
|
type: "npm",
|
|
125
|
-
script: "dev",
|
|
142
|
+
script: "dev:vscode",
|
|
126
143
|
problemMatcher: {
|
|
127
144
|
owner: "typescript",
|
|
128
145
|
fileLocation: "relative",
|
|
@@ -146,7 +163,7 @@ async function generateTasksJson(apps) {
|
|
|
146
163
|
kind: "build",
|
|
147
164
|
isDefault: true
|
|
148
165
|
},
|
|
149
|
-
options: { cwd:
|
|
166
|
+
options: { cwd: `\${workspaceFolder}/${hostApp.path}` }
|
|
150
167
|
}]
|
|
151
168
|
};
|
|
152
169
|
}
|
|
@@ -160,7 +177,7 @@ async function generateVscodeConfig(root) {
|
|
|
160
177
|
if (tasks) {
|
|
161
178
|
await fs.writeFile(path.join(vsCodeDir, "tasks.json"), JSON.stringify(tasks, null, 2), "utf8");
|
|
162
179
|
console.log("Wrote .vscode/tasks.json");
|
|
163
|
-
} else console.log("Skipped .vscode/tasks.json (no
|
|
180
|
+
} else console.log("Skipped .vscode/tasks.json (no app with a dev:vscode script)");
|
|
164
181
|
}
|
|
165
182
|
|
|
166
183
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@multiplatform.one/cli",
|
|
3
|
-
"version": "6.
|
|
4
|
-
"description": "multiplatform.one cli",
|
|
5
|
-
"keywords": [
|
|
3
|
+
"version": "6.4.0",
|
|
4
|
+
"description": "multiplatform.one cli — mpo init / create-multiplatform-app",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"create-multiplatform-app",
|
|
7
|
+
"mpo",
|
|
8
|
+
"multiplatform.one",
|
|
9
|
+
"scaffold"
|
|
10
|
+
],
|
|
6
11
|
"bugs": {
|
|
7
12
|
"url": "https://gitlab.com/bitspur/multiplatform.one/multiplatform.one/issues"
|
|
8
13
|
},
|
|
@@ -16,9 +21,11 @@
|
|
|
16
21
|
],
|
|
17
22
|
"repository": {
|
|
18
23
|
"type": "git",
|
|
19
|
-
"url": "https://gitlab.com/bitspur/
|
|
24
|
+
"url": "git+https://gitlab.com/bitspur/frappe/multiplatform.one.git",
|
|
25
|
+
"directory": "public/cli"
|
|
20
26
|
},
|
|
21
27
|
"bin": {
|
|
28
|
+
"create-multiplatform-app": "./bin/createMultiplatformApp.mjs",
|
|
22
29
|
"mpo": "./bin/multiplatformOne.mjs"
|
|
23
30
|
},
|
|
24
31
|
"source": "src/index.ts",
|
|
@@ -27,19 +34,21 @@
|
|
|
27
34
|
"lib",
|
|
28
35
|
"scripts",
|
|
29
36
|
"src",
|
|
30
|
-
"
|
|
37
|
+
"templates",
|
|
38
|
+
"types",
|
|
39
|
+
"!types/.tsbuildinfo"
|
|
31
40
|
],
|
|
32
41
|
"type": "module",
|
|
33
42
|
"module": "lib/index.mjs",
|
|
34
43
|
"types": "types/index.d.ts",
|
|
35
44
|
"exports": {
|
|
36
45
|
".": {
|
|
37
|
-
"
|
|
38
|
-
"
|
|
46
|
+
"types": "./types/index.d.ts",
|
|
47
|
+
"import": "./lib/index.mjs"
|
|
39
48
|
},
|
|
40
49
|
"./bin/multiplatformOne": {
|
|
41
|
-
"
|
|
42
|
-
"
|
|
50
|
+
"types": "./types/bin/multiplatformOne.d.ts",
|
|
51
|
+
"import": "./lib/bin/multiplatformOne.mjs"
|
|
43
52
|
},
|
|
44
53
|
"./package.json": "./package.json"
|
|
45
54
|
},
|
|
@@ -52,18 +61,20 @@
|
|
|
52
61
|
"nano-spawn": "^2.1.0",
|
|
53
62
|
"yaml": "^2.8.3",
|
|
54
63
|
"yocto-spinner": "^1.1.0",
|
|
55
|
-
"@multiplatform.one/utils": "6.
|
|
64
|
+
"@multiplatform.one/utils": "6.4.0"
|
|
56
65
|
},
|
|
57
66
|
"devDependencies": {
|
|
58
67
|
"@types/inquirer": "^9.0.9",
|
|
59
68
|
"@types/node": "^25.6.0",
|
|
60
|
-
"typescript": "~5.9.3"
|
|
69
|
+
"typescript": "~5.9.3",
|
|
70
|
+
"vitest": "^4.1.5"
|
|
61
71
|
},
|
|
62
72
|
"engines": {
|
|
63
73
|
"node": ">= 16.0"
|
|
64
74
|
},
|
|
65
75
|
"scripts": {
|
|
66
|
-
"build": "rm -rf lib types 2>/dev/null && tsc -
|
|
76
|
+
"build": "rm -rf lib types 2>/dev/null && tsc -p tsconfig.build.json && tsdown",
|
|
77
|
+
"test": "vitest run",
|
|
67
78
|
"typecheck": "tsc --noEmit"
|
|
68
79
|
}
|
|
69
80
|
}
|
|
@@ -19,6 +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, readProvenance } from "../commands/initApp";
|
|
23
|
+
import { updateApp } from "../commands/updateApp";
|
|
22
24
|
|
|
23
25
|
const projectRoot = lookupProjectRoot();
|
|
24
26
|
const availableServices = ["api", "frappe", "solana", "ethereum", "sui"];
|
|
@@ -216,35 +218,84 @@ program.version(
|
|
|
216
218
|
|
|
217
219
|
program
|
|
218
220
|
.command("init")
|
|
219
|
-
.option("
|
|
220
|
-
.option("
|
|
221
|
-
.option("
|
|
221
|
+
.option("--universal", "scaffold the universal template (web + iOS + Android; default)")
|
|
222
|
+
.option("--web", "scaffold the web-only template (Vite + React)")
|
|
223
|
+
.option("--app", "alias of --web (back-compat)")
|
|
224
|
+
.option("--skip-install", "skip pnpm install after scaffolding")
|
|
225
|
+
.option(
|
|
226
|
+
"--mpo-version <range>",
|
|
227
|
+
"semver range for @multiplatform.one/* (default: ^<cli version>)",
|
|
228
|
+
)
|
|
229
|
+
.option("-y, --yes", "non-interactive: accept defaults instead of prompting")
|
|
230
|
+
.option(
|
|
231
|
+
"--monorepo",
|
|
232
|
+
"DEPRECATED: clone/modify the full multiplatform.one monorepo instead of scaffolding a consumer project",
|
|
233
|
+
)
|
|
234
|
+
.option("-c, --checkout <branch>", "branch, tag or commit to checkout (--monorepo only)", "main")
|
|
235
|
+
.option("-a, --apps <apps>", "comma-separated apps to include (--monorepo only)")
|
|
236
|
+
.option("-s, --services <services>", "comma-separated services to include (--monorepo only)")
|
|
222
237
|
.argument("[name]", "the name of the project")
|
|
223
|
-
.description(
|
|
238
|
+
.description(
|
|
239
|
+
"scaffold a new multiplatform project (universal web + native by default; --web for web-only; semver @multiplatform.one/* deps)",
|
|
240
|
+
)
|
|
224
241
|
.action(async (name: string | undefined, options) => {
|
|
225
|
-
if (
|
|
226
|
-
|
|
227
|
-
()
|
|
228
|
-
|
|
229
|
-
)
|
|
230
|
-
|
|
231
|
-
|
|
242
|
+
if (options.monorepo) {
|
|
243
|
+
console.warn(
|
|
244
|
+
"⚠️ `mpo init --monorepo` clones the whole multiplatform.one monorepo (legacy). " +
|
|
245
|
+
"New projects should use the default consumer scaffold instead.",
|
|
246
|
+
);
|
|
247
|
+
if (
|
|
248
|
+
await spawn("git", ["rev-parse", "--is-inside-work-tree"]).then(
|
|
249
|
+
() => true,
|
|
250
|
+
() => false,
|
|
251
|
+
)
|
|
252
|
+
) {
|
|
253
|
+
throw new Error("mpo cannot be initialized inside a git repository");
|
|
254
|
+
}
|
|
255
|
+
const cloneScript = path.resolve(
|
|
256
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
257
|
+
"../../scripts/clone.sh",
|
|
258
|
+
);
|
|
259
|
+
await init(name, { ...options, cloneScript });
|
|
260
|
+
return;
|
|
232
261
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
262
|
+
if (options.apps || options.services) {
|
|
263
|
+
throw new Error(
|
|
264
|
+
"--apps/--services select monorepo pieces and require --monorepo (legacy clone mode)",
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
const webOnly = Boolean(options.web || options.app);
|
|
268
|
+
if (webOnly && options.universal) {
|
|
269
|
+
throw new Error("Pass either --web/--app or --universal, not both");
|
|
270
|
+
}
|
|
271
|
+
await initApp(name, {
|
|
272
|
+
skipInstall: Boolean(options.skipInstall),
|
|
273
|
+
version: options.mpoVersion,
|
|
274
|
+
template: webOnly ? "app" : options.universal ? "universal" : undefined,
|
|
275
|
+
yes: Boolean(options.yes),
|
|
276
|
+
});
|
|
238
277
|
});
|
|
239
278
|
|
|
240
279
|
const defaultUpdateRemote = "https://gitlab.com/bitspur/multiplatform.one/multiplatform.one.git";
|
|
241
280
|
|
|
242
281
|
program
|
|
243
282
|
.command("update")
|
|
244
|
-
.option("-c, --checkout <branch>", "branch, tag or commit to merge from upstream", "main")
|
|
245
|
-
.option("-r, --remote <url>", "upstream remote URL", defaultUpdateRemote)
|
|
246
|
-
.
|
|
247
|
-
.
|
|
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
|
+
}) => {
|
|
248
299
|
// Update prechecks: must be in a git repo
|
|
249
300
|
if (
|
|
250
301
|
await spawn("git", ["rev-parse", "--is-inside-work-tree"]).then(
|
|
@@ -254,6 +305,18 @@ program
|
|
|
254
305
|
) {
|
|
255
306
|
throw new Error("mpo cannot be updated outside of a git repository");
|
|
256
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
|
+
}
|
|
257
320
|
// Require clean working tree (no staged or unstaged changes)
|
|
258
321
|
if (
|
|
259
322
|
await spawn("git", ["diff", "--cached", "--quiet"]).then(
|
package/src/commands/init.ts
CHANGED
|
@@ -11,15 +11,10 @@ const services = ["frappe", "solana", "ethereum", "sui"] as const;
|
|
|
11
11
|
function serviceDir(name: string): string {
|
|
12
12
|
return `apps/${name}`;
|
|
13
13
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"storybook-expo",
|
|
19
|
-
"vscode",
|
|
20
|
-
"webext",
|
|
21
|
-
"vocs",
|
|
22
|
-
] as const;
|
|
14
|
+
// vscode + webext are no longer standalone apps: they are delivery targets
|
|
15
|
+
// of the One platform app (apps/one/vscode, apps/one/webext) and ride with
|
|
16
|
+
// the "one" selection.
|
|
17
|
+
const apps = ["one", "keycloak", "storybook", "storybook-expo", "vocs"] as const;
|
|
23
18
|
|
|
24
19
|
type Service = (typeof services)[number];
|
|
25
20
|
type App = (typeof apps)[number];
|
|
@@ -34,12 +29,10 @@ const serviceEnvPrefixes: Record<Service, string[]> = {
|
|
|
34
29
|
|
|
35
30
|
/** env variable prefixes to remove per app */
|
|
36
31
|
const appEnvPrefixes: Record<App, string[]> = {
|
|
37
|
-
one: ["ONE_"],
|
|
32
|
+
one: ["ONE_", "VSCODE_", "WEBEXT_"],
|
|
38
33
|
keycloak: ["KEYCLOAK_"],
|
|
39
34
|
storybook: ["STORYBOOK_", "VR_DIFFING_ENGINE="],
|
|
40
35
|
"storybook-expo": [],
|
|
41
|
-
vscode: ["VSCODE_"],
|
|
42
|
-
webext: ["WEBEXT_"],
|
|
43
36
|
vocs: ["VOCS_"],
|
|
44
37
|
};
|
|
45
38
|
|
|
@@ -52,12 +45,10 @@ const serviceEnvSections: Record<Service, string[]> = {
|
|
|
52
45
|
};
|
|
53
46
|
|
|
54
47
|
const appEnvSections: Record<App, string[]> = {
|
|
55
|
-
one: ["# one"],
|
|
48
|
+
one: ["# one", "# vscode", "# webext"],
|
|
56
49
|
keycloak: ["# keycloak"],
|
|
57
50
|
storybook: ["# storybook"],
|
|
58
51
|
"storybook-expo": [],
|
|
59
|
-
vscode: ["# vscode"],
|
|
60
|
-
webext: ["# webext"],
|
|
61
52
|
vocs: ["# vocs"],
|
|
62
53
|
};
|
|
63
54
|
|
|
@@ -70,12 +61,10 @@ const serviceLaunchNames: Record<Service, string[]> = {
|
|
|
70
61
|
};
|
|
71
62
|
|
|
72
63
|
const appLaunchNames: Record<App, string[]> = {
|
|
73
|
-
one: ["one dev"],
|
|
64
|
+
one: ["one dev", "vscode dev", "webext dev"],
|
|
74
65
|
keycloak: ["keycloak dev", "keycloak storybook"],
|
|
75
66
|
storybook: ["storybook dev"],
|
|
76
67
|
"storybook-expo": ["storybook-expo dev"],
|
|
77
|
-
vscode: ["vscode dev"],
|
|
78
|
-
webext: ["webext dev"],
|
|
79
68
|
vocs: ["vocs dev"],
|
|
80
69
|
};
|
|
81
70
|
|