@multiplatform.one/cli 7.11.0 → 7.15.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
package/lib/commands/init.mjs
CHANGED
|
@@ -1,484 +1,167 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { mpoRange, validateName } from "./provenance.mjs";
|
|
2
|
+
import { resolveSource } from "../source.mjs";
|
|
3
|
+
import { ScaffoldError, runScaffold } from "./scaffold.mjs";
|
|
4
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
5
|
+
import { basename, resolve } from "node:path";
|
|
6
|
+
import spawn from "nano-spawn";
|
|
3
7
|
import inquirer from "inquirer";
|
|
4
|
-
import { execSync } from "node:child_process";
|
|
5
8
|
|
|
6
9
|
//#region src/commands/init.ts
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* `mpo init`: a consumer project is the multiplatform.one monorepo at one
|
|
12
|
+
* commit, pruned. scripts/scaffold.sh does the work (clone or archive at
|
|
13
|
+
* the ref, drop public/ and the singleton apps not asked for, rename
|
|
14
|
+
* apps/one to each product app, rewrite workspace: ranges to semver); this
|
|
15
|
+
* command picks the commit, asks what to keep, and finishes the repo.
|
|
16
|
+
*/
|
|
17
|
+
/** The apps that exist once per project; everything else under apps/ is a product app. */
|
|
18
|
+
const SINGLETON_APPS = [
|
|
11
19
|
"ethereum",
|
|
12
|
-
"
|
|
13
|
-
];
|
|
14
|
-
/** Resolve a service name to its relative directory from the project root. */
|
|
15
|
-
function serviceDir(name) {
|
|
16
|
-
return `apps/${name}`;
|
|
17
|
-
}
|
|
18
|
-
const apps = [
|
|
19
|
-
"one",
|
|
20
|
+
"frappe",
|
|
20
21
|
"keycloak",
|
|
22
|
+
"solana",
|
|
21
23
|
"storybook",
|
|
22
24
|
"storybook-expo",
|
|
25
|
+
"sui",
|
|
26
|
+
"uxpin",
|
|
23
27
|
"vocs"
|
|
24
28
|
];
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
frappe:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"ONE_",
|
|
36
|
-
"VSCODE_",
|
|
37
|
-
"WEBEXT_"
|
|
38
|
-
],
|
|
39
|
-
keycloak: ["KEYCLOAK_"],
|
|
40
|
-
storybook: ["STORYBOOK_", "VR_DIFFING_ENGINE="],
|
|
41
|
-
"storybook-expo": [],
|
|
42
|
-
vocs: ["VOCS_"]
|
|
43
|
-
};
|
|
44
|
-
/** env section headers (comments) to remove per app/service */
|
|
45
|
-
const serviceEnvSections = {
|
|
46
|
-
frappe: ["# frappe", "# mariadb"],
|
|
47
|
-
solana: [],
|
|
48
|
-
ethereum: [],
|
|
49
|
-
sui: []
|
|
50
|
-
};
|
|
51
|
-
const appEnvSections = {
|
|
52
|
-
one: [
|
|
53
|
-
"# one",
|
|
54
|
-
"# vscode",
|
|
55
|
-
"# webext"
|
|
56
|
-
],
|
|
57
|
-
keycloak: ["# keycloak"],
|
|
58
|
-
storybook: ["# storybook"],
|
|
59
|
-
"storybook-expo": [],
|
|
60
|
-
vocs: ["# vocs"]
|
|
29
|
+
const SINGLETON_DESCRIPTIONS = {
|
|
30
|
+
ethereum: "Hardhat chain + contracts",
|
|
31
|
+
frappe: "Frappe bench (backend, docker compose)",
|
|
32
|
+
keycloak: "Keycloak theme + realm (auth, docker compose)",
|
|
33
|
+
solana: "Anchor programs + localnet",
|
|
34
|
+
storybook: "Storybook (web)",
|
|
35
|
+
"storybook-expo": "Storybook (native)",
|
|
36
|
+
sui: "Move packages + localnet",
|
|
37
|
+
uxpin: "UXPin Merge library",
|
|
38
|
+
vocs: "Docs site"
|
|
61
39
|
};
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
frappe: ["frappe dev"],
|
|
65
|
-
solana: ["solana dev", "solana localnet"],
|
|
66
|
-
ethereum: ["ethereum dev"],
|
|
67
|
-
sui: ["sui dev"]
|
|
68
|
-
};
|
|
69
|
-
const appLaunchNames = {
|
|
70
|
-
one: [
|
|
71
|
-
"one dev",
|
|
72
|
-
"vscode dev",
|
|
73
|
-
"webext dev"
|
|
74
|
-
],
|
|
75
|
-
keycloak: ["keycloak dev", "keycloak storybook"],
|
|
76
|
-
storybook: ["storybook dev"],
|
|
77
|
-
"storybook-expo": ["storybook-expo dev"],
|
|
78
|
-
vocs: ["vocs dev"]
|
|
79
|
-
};
|
|
80
|
-
/**
|
|
81
|
-
* Shared modify step: remove unselected apps/services and rewrite configs.
|
|
82
|
-
* Used by both init (with prompted selections) and update (with getPresentApps/getPresentServices).
|
|
83
|
-
*/
|
|
84
|
-
function runModifyStep(dir, selectedServices, selectedApps) {
|
|
85
|
-
const services = selectedServices;
|
|
86
|
-
const apps = selectedApps;
|
|
87
|
-
for (const service of services) if (!services.includes(service)) {
|
|
88
|
-
const servicePath = join(dir, serviceDir(service));
|
|
89
|
-
if (existsSync(servicePath)) {
|
|
90
|
-
rmSync(servicePath, { recursive: true });
|
|
91
|
-
console.log(`Removed service: ${service}`);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
for (const app of apps) if (!apps.includes(app)) {
|
|
95
|
-
const appPath = join(dir, "apps", app);
|
|
96
|
-
if (existsSync(appPath)) {
|
|
97
|
-
rmSync(appPath, { recursive: true });
|
|
98
|
-
console.log(`Removed app: ${app}`);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
const packagesDir = join(dir, "packages");
|
|
102
|
-
if (existsSync(packagesDir)) {
|
|
103
|
-
const versionMap = /* @__PURE__ */ new Map();
|
|
104
|
-
for (const pkg of readdirSync(packagesDir)) {
|
|
105
|
-
const pkgJsonPath = join(packagesDir, pkg, "package.json");
|
|
106
|
-
if (existsSync(pkgJsonPath)) {
|
|
107
|
-
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
108
|
-
if (pkgJson.name && pkgJson.version) versionMap.set(pkgJson.name, pkgJson.version);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
for (const pkg of readdirSync(packagesDir)) {
|
|
112
|
-
const pkgJsonPath = join(packagesDir, pkg, "package.json");
|
|
113
|
-
if (existsSync(pkgJsonPath)) {
|
|
114
|
-
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
115
|
-
if (!pkgJson.private) {
|
|
116
|
-
rmSync(join(packagesDir, pkg), { recursive: true });
|
|
117
|
-
console.log(`Removed public package: ${pkgJson.name}`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
convertWorkspaceVersions(dir, versionMap);
|
|
122
|
-
}
|
|
123
|
-
cleanupPnpmWorkspace(dir, services, apps);
|
|
124
|
-
cleanupLaunchJson(dir, services, apps);
|
|
125
|
-
cleanupEnvFiles(dir, services, apps);
|
|
126
|
-
cleanupTsconfigs(dir);
|
|
127
|
-
cleanupMisc(dir);
|
|
128
|
-
removeLicenseHeaders(dir);
|
|
40
|
+
function unique(values) {
|
|
41
|
+
return [...new Set(values)];
|
|
129
42
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (!
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
let selectedServices;
|
|
139
|
-
if (options.services) selectedServices = options.services.split(",").map((s) => s.trim());
|
|
140
|
-
else selectedServices = (await inquirer.prompt([{
|
|
141
|
-
message: "Select services:",
|
|
43
|
+
function validateServices(services) {
|
|
44
|
+
const known = new Set(SINGLETON_APPS);
|
|
45
|
+
for (const service of services) if (!known.has(service)) throw new Error(`Unknown service "${service}". Services are the singleton apps: ${SINGLETON_APPS.join(", ")}`);
|
|
46
|
+
return unique(services).sort();
|
|
47
|
+
}
|
|
48
|
+
async function promptServices() {
|
|
49
|
+
return validateServices((await inquirer.prompt([{
|
|
50
|
+
message: "Which singleton apps should the project keep? (space to toggle, enter to confirm)",
|
|
142
51
|
name: "services",
|
|
143
52
|
type: "checkbox",
|
|
144
|
-
choices:
|
|
145
|
-
name:
|
|
146
|
-
value:
|
|
147
|
-
|
|
148
|
-
}])).services;
|
|
149
|
-
let selectedApps;
|
|
150
|
-
if (options.apps) selectedApps = options.apps.split(",").map((s) => s.trim());
|
|
151
|
-
else selectedApps = (await inquirer.prompt([{
|
|
152
|
-
message: "Select apps:",
|
|
153
|
-
name: "apps",
|
|
154
|
-
type: "checkbox",
|
|
155
|
-
choices: apps.map((a) => ({
|
|
156
|
-
name: a,
|
|
157
|
-
value: a,
|
|
158
|
-
checked: a === "one"
|
|
53
|
+
choices: SINGLETON_APPS.map((service) => ({
|
|
54
|
+
name: `${service} — ${SINGLETON_DESCRIPTIONS[service]}`,
|
|
55
|
+
value: service,
|
|
56
|
+
checked: false
|
|
159
57
|
}))
|
|
160
|
-
}])).
|
|
161
|
-
const targetDir = resolve(projectName);
|
|
162
|
-
if (existsSync(targetDir)) {
|
|
163
|
-
if (readdirSync(targetDir).length > 0) throw new Error(`Directory "${targetDir}" already exists and is not empty`);
|
|
164
|
-
}
|
|
165
|
-
const branch = options.checkout || "main";
|
|
166
|
-
console.log("\nCloning repository...");
|
|
167
|
-
if (options.cloneScript) execSync(`sh "${options.cloneScript}" "${repoUrl}" "${branch}" "${targetDir}"`, { stdio: "inherit" });
|
|
168
|
-
else execSync(`git clone --depth 1 --branch "${branch}" ${repoUrl} "${targetDir}"`, { stdio: "inherit" });
|
|
169
|
-
rmSync(join(targetDir, ".git"), {
|
|
170
|
-
recursive: true,
|
|
171
|
-
force: true
|
|
172
|
-
});
|
|
173
|
-
runModifyStep(targetDir, selectedServices, selectedApps);
|
|
174
|
-
const rootPkgPath = join(targetDir, "package.json");
|
|
175
|
-
if (existsSync(rootPkgPath)) {
|
|
176
|
-
const rootPkg = JSON.parse(readFileSync(rootPkgPath, "utf-8"));
|
|
177
|
-
rootPkg.name = projectName;
|
|
178
|
-
rootPkg.packageManager = void 0;
|
|
179
|
-
writeFileSync(rootPkgPath, `${JSON.stringify(rootPkg, null, 2)}\n`);
|
|
180
|
-
}
|
|
181
|
-
const envDefaultPath = join(targetDir, ".env.example");
|
|
182
|
-
const envPath = join(targetDir, ".env");
|
|
183
|
-
if (existsSync(envDefaultPath) && !existsSync(envPath)) writeFileSync(envPath, readFileSync(envDefaultPath, "utf-8"));
|
|
184
|
-
const lostpixelBaseline = join(targetDir, "apps", "storybook", ".lostpixel", "baseline");
|
|
185
|
-
if (existsSync(lostpixelBaseline)) rmSync(lostpixelBaseline, { recursive: true });
|
|
186
|
-
const lockfile = join(targetDir, "pnpm-lock.yaml");
|
|
187
|
-
if (existsSync(lockfile)) rmSync(lockfile);
|
|
188
|
-
for (const artifact of [
|
|
189
|
-
"agent-os",
|
|
190
|
-
"public/cli/scripts/init.sh",
|
|
191
|
-
"public/cli/scripts/update.sh",
|
|
192
|
-
".changeset"
|
|
193
|
-
]) {
|
|
194
|
-
const p = join(targetDir, artifact);
|
|
195
|
-
if (existsSync(p)) rmSync(p, {
|
|
196
|
-
recursive: true,
|
|
197
|
-
force: true
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
console.log("\nInstalling dependencies...");
|
|
201
|
-
execSync("pnpm install", {
|
|
202
|
-
cwd: targetDir,
|
|
203
|
-
stdio: "inherit"
|
|
204
|
-
});
|
|
205
|
-
console.log("\nInitializing git repository...");
|
|
206
|
-
execSync("git init", {
|
|
207
|
-
cwd: targetDir,
|
|
208
|
-
stdio: "inherit"
|
|
209
|
-
});
|
|
210
|
-
execSync("git add -A", {
|
|
211
|
-
cwd: targetDir,
|
|
212
|
-
stdio: "inherit"
|
|
213
|
-
});
|
|
214
|
-
execSync("git commit -m \"Initial commit from multiplatform.one\"", {
|
|
215
|
-
cwd: targetDir,
|
|
216
|
-
stdio: "inherit"
|
|
217
|
-
});
|
|
218
|
-
console.log(`\n✅ Project created at ${targetDir}`);
|
|
219
|
-
console.log("\nNext steps:");
|
|
220
|
-
console.log(` cd ${projectName}`);
|
|
221
|
-
console.log(" pnpm dev");
|
|
58
|
+
}])).services);
|
|
222
59
|
}
|
|
223
|
-
function
|
|
224
|
-
|
|
225
|
-
for (const file of files) {
|
|
226
|
-
const content = JSON.parse(readFileSync(file, "utf-8"));
|
|
227
|
-
let changed = false;
|
|
228
|
-
for (const depType of [
|
|
229
|
-
"dependencies",
|
|
230
|
-
"devDependencies",
|
|
231
|
-
"peerDependencies"
|
|
232
|
-
]) {
|
|
233
|
-
const deps = content[depType];
|
|
234
|
-
if (!deps) continue;
|
|
235
|
-
for (const [depName, version] of Object.entries(deps)) if (typeof version === "string" && version.startsWith("workspace:")) {
|
|
236
|
-
const realVersion = versionMap.get(depName);
|
|
237
|
-
if (realVersion) {
|
|
238
|
-
const workspaceRange = version.replace("workspace:", "");
|
|
239
|
-
deps[depName] = workspaceRange === "*" || workspaceRange === "^" ? `^${realVersion}` : workspaceRange;
|
|
240
|
-
changed = true;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
if (changed) writeFileSync(file, `${JSON.stringify(content, null, 2)}\n`);
|
|
245
|
-
}
|
|
60
|
+
async function insideGitRepo(dir) {
|
|
61
|
+
return spawn("git", ["rev-parse", "--is-inside-work-tree"], { cwd: dir }).then(() => true, () => false);
|
|
246
62
|
}
|
|
247
|
-
function
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
walk(dir);
|
|
264
|
-
return results;
|
|
265
|
-
}
|
|
266
|
-
function cleanupPnpmWorkspace(dir, _services, _apps) {
|
|
267
|
-
const wsPath = join(dir, "pnpm-workspace.yaml");
|
|
268
|
-
if (!existsSync(wsPath)) return;
|
|
269
|
-
let content = readFileSync(wsPath, "utf-8");
|
|
270
|
-
const appsDir = join(dir, "apps");
|
|
271
|
-
if (!existsSync(appsDir) || readdirSync(appsDir).length === 0) content = content.replace(/^\s*-\s*['"]?apps\/\*['"]?\s*$/gm, "");
|
|
272
|
-
const pkgDir = join(dir, "packages");
|
|
273
|
-
if (!existsSync(pkgDir) || readdirSync(pkgDir).length === 0) content = content.replace(/^\s*-\s*['"]?packages\/\*['"]?\s*$/gm, "");
|
|
274
|
-
content = `${content.replace(/\n{3,}/g, "\n\n").trim()}\n`;
|
|
275
|
-
writeFileSync(wsPath, content);
|
|
276
|
-
}
|
|
277
|
-
function cleanupLaunchJson(dir, services, apps) {
|
|
278
|
-
const launchPath = join(dir, ".vscode", "launch.json");
|
|
279
|
-
if (!existsSync(launchPath)) return;
|
|
280
|
-
const launch = JSON.parse(readFileSync(launchPath, "utf-8"));
|
|
281
|
-
const removedNames = /* @__PURE__ */ new Set();
|
|
282
|
-
for (const service of services) if (!services.includes(service)) for (const configName of serviceLaunchNames[service]) removedNames.add(configName);
|
|
283
|
-
for (const app of apps) if (!apps.includes(app)) for (const configName of appLaunchNames[app]) removedNames.add(configName);
|
|
284
|
-
if (launch.configurations) launch.configurations = launch.configurations.filter((c) => !removedNames.has(c.name));
|
|
285
|
-
if (launch.compounds) {
|
|
286
|
-
for (const compound of launch.compounds) if (compound.configurations) compound.configurations = compound.configurations.filter((configName) => !removedNames.has(configName));
|
|
287
|
-
launch.compounds = launch.compounds.filter((c) => c.configurations && c.configurations.length > 0);
|
|
63
|
+
async function scaffoldWithFallback(spec, request) {
|
|
64
|
+
try {
|
|
65
|
+
return await runScaffold({
|
|
66
|
+
...request,
|
|
67
|
+
source: spec.source,
|
|
68
|
+
ref: spec.ref
|
|
69
|
+
});
|
|
70
|
+
} catch (err) {
|
|
71
|
+
if (spec.origin !== "tag" || !(err instanceof ScaffoldError)) throw err;
|
|
72
|
+
console.warn(`⚠️ ${err.message}; retrying from main`);
|
|
73
|
+
return runScaffold({
|
|
74
|
+
...request,
|
|
75
|
+
source: spec.source,
|
|
76
|
+
ref: "main"
|
|
77
|
+
});
|
|
288
78
|
}
|
|
289
|
-
writeFileSync(launchPath, `${JSON.stringify(launch, null, 2)}\n`);
|
|
290
79
|
}
|
|
291
|
-
function
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
80
|
+
async function init(nameArg, options = {}) {
|
|
81
|
+
const interactive = !options.yes && process.stdout.isTTY;
|
|
82
|
+
let targetDir;
|
|
83
|
+
let name;
|
|
84
|
+
if (options.into) {
|
|
85
|
+
targetDir = resolve(nameArg ?? ".");
|
|
86
|
+
name = validateName(options.apps?.[0] ?? basename(targetDir));
|
|
87
|
+
} else {
|
|
88
|
+
let projectName = nameArg;
|
|
89
|
+
if (!projectName) if (!interactive) projectName = "my-app";
|
|
90
|
+
else projectName = (await inquirer.prompt([{
|
|
91
|
+
message: "What is the app name?",
|
|
92
|
+
name: "name",
|
|
93
|
+
type: "input",
|
|
94
|
+
default: "my-app"
|
|
95
|
+
}])).name;
|
|
96
|
+
name = validateName(projectName);
|
|
97
|
+
targetDir = resolve(name);
|
|
307
98
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
99
|
+
const apps = unique([name, ...(options.apps ?? []).map(validateName)]);
|
|
100
|
+
let services;
|
|
101
|
+
if (options.services) services = validateServices(options.services);
|
|
102
|
+
else if (interactive) services = await promptServices();
|
|
103
|
+
else services = [];
|
|
104
|
+
if (existsSync(targetDir) && readdirSync(targetDir).length > 0 && !options.into) throw new Error(`Directory "${targetDir}" already exists and is not empty. Pass --into to scaffold into it (paths that already exist are kept and pinned in .updateignore), or pick another name.`);
|
|
105
|
+
const spec = resolveSource({
|
|
106
|
+
source: options.source,
|
|
107
|
+
checkout: options.checkout
|
|
317
108
|
});
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
109
|
+
if (spec.warning) console.warn(`⚠️ ${spec.warning}`);
|
|
110
|
+
console.log(`\nScaffolding ${targetDir} from ${spec.source} @ ${spec.ref}`);
|
|
111
|
+
console.log(` apps ${apps.join(", ")}`);
|
|
112
|
+
console.log(` services ${services.length ? services.join(", ") : "(none)"}`);
|
|
113
|
+
const sha = (await scaffoldWithFallback(spec, {
|
|
114
|
+
source: spec.source,
|
|
115
|
+
ref: spec.ref,
|
|
116
|
+
target: targetDir,
|
|
117
|
+
apps,
|
|
118
|
+
services,
|
|
119
|
+
mpoVersion: options.mpoVersion ? mpoRange(options.mpoVersion) : void 0,
|
|
120
|
+
into: options.into,
|
|
121
|
+
verbose: options.verbose
|
|
122
|
+
})).source.sha;
|
|
123
|
+
console.log(` source ${sha}`);
|
|
124
|
+
let scaffoldCommitted = false;
|
|
125
|
+
if (!options.skipGit && !await insideGitRepo(targetDir)) {
|
|
126
|
+
await spawn("git", ["init", "--quiet"], { cwd: targetDir });
|
|
127
|
+
await spawn("git", ["add", "-A"], { cwd: targetDir });
|
|
128
|
+
scaffoldCommitted = await spawn("git", [
|
|
129
|
+
"commit",
|
|
130
|
+
"--quiet",
|
|
131
|
+
"-m",
|
|
132
|
+
`feat: scaffold from multiplatform.one ${sha.slice(0, 12)} (mpo init)`
|
|
133
|
+
], { cwd: targetDir }).then(() => true, () => {
|
|
134
|
+
console.warn("⚠️ git commit failed (missing git identity?) — repo left staged.");
|
|
135
|
+
return false;
|
|
331
136
|
});
|
|
332
|
-
if (changed) writeFileSync(file, `${JSON.stringify(json, null, 2)}\n`);
|
|
333
|
-
} catch {}
|
|
334
|
-
}
|
|
335
|
-
function findTsconfigFiles(dir) {
|
|
336
|
-
const results = [];
|
|
337
|
-
function walk(d) {
|
|
338
|
-
let entries;
|
|
339
|
-
try {
|
|
340
|
-
entries = readdirSync(d, { withFileTypes: true });
|
|
341
|
-
} catch {
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
for (const entry of entries) {
|
|
345
|
-
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
346
|
-
const full = join(d, entry.name);
|
|
347
|
-
if (entry.isDirectory()) walk(full);
|
|
348
|
-
else if (entry.name.startsWith("tsconfig") && entry.name.endsWith(".json")) results.push(full);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
walk(dir);
|
|
352
|
-
return results;
|
|
353
|
-
}
|
|
354
|
-
function stripJsonComments(str) {
|
|
355
|
-
return str.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
356
|
-
}
|
|
357
|
-
function cleanupMisc(dir) {
|
|
358
|
-
for (const subdir of ["docker/dns"]) {
|
|
359
|
-
const p = join(dir, subdir);
|
|
360
|
-
if (existsSync(p)) rmSync(p, { recursive: true });
|
|
361
137
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
"
|
|
381
|
-
|
|
382
|
-
"
|
|
383
|
-
"
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
const content = readFileSync(file, "utf-8");
|
|
389
|
-
const match = content.match(/^\s*\/\*[\s\S]*?\*\//);
|
|
390
|
-
if (match) {
|
|
391
|
-
const comment = match[0];
|
|
392
|
-
if (licenseIndicators.some((indicator) => comment.includes(indicator)) && match.index !== void 0) writeFileSync(file, content.slice(match.index + comment.length).replace(/^\s*\n/, ""));
|
|
393
|
-
}
|
|
394
|
-
} catch {}
|
|
395
|
-
const hashFiles = findHashCommentFiles(dir);
|
|
396
|
-
for (const file of hashFiles) try {
|
|
397
|
-
const lines = readFileSync(file, "utf-8").split("\n");
|
|
398
|
-
let headerEnd = 0;
|
|
399
|
-
let inHeader = false;
|
|
400
|
-
for (let i = 0; i < lines.length; i++) {
|
|
401
|
-
const line = lines[i];
|
|
402
|
-
if (i === 0 && line.startsWith("#!")) continue;
|
|
403
|
-
if (line.startsWith("# File:") || line.startsWith("# Project:") || line.startsWith("# File Created:") || line.startsWith("# Author:")) {
|
|
404
|
-
inHeader = true;
|
|
405
|
-
headerEnd = i + 1;
|
|
406
|
-
continue;
|
|
407
|
-
}
|
|
408
|
-
if (inHeader && line.startsWith("#")) {
|
|
409
|
-
headerEnd = i + 1;
|
|
410
|
-
continue;
|
|
411
|
-
}
|
|
412
|
-
if (inHeader && line.trim() === "") {
|
|
413
|
-
headerEnd = i + 1;
|
|
414
|
-
break;
|
|
415
|
-
}
|
|
416
|
-
if (inHeader) break;
|
|
417
|
-
}
|
|
418
|
-
if (inHeader && headerEnd > 0) writeFileSync(file, `${lines[0].startsWith("#!") ? `${lines[0]}\n\n` : ""}${lines.slice(headerEnd).join("\n").replace(/^\s*\n/, "")}`);
|
|
419
|
-
} catch {}
|
|
420
|
-
}
|
|
421
|
-
function findSourceFiles(dir) {
|
|
422
|
-
const results = [];
|
|
423
|
-
const extensions = new Set([
|
|
424
|
-
".ts",
|
|
425
|
-
".tsx",
|
|
426
|
-
".js",
|
|
427
|
-
".jsx",
|
|
428
|
-
".mts",
|
|
429
|
-
".mjs",
|
|
430
|
-
".cts",
|
|
431
|
-
".cjs"
|
|
432
|
-
]);
|
|
433
|
-
function walk(d) {
|
|
434
|
-
let entries;
|
|
435
|
-
try {
|
|
436
|
-
entries = readdirSync(d, { withFileTypes: true });
|
|
437
|
-
} catch {
|
|
438
|
-
return;
|
|
439
|
-
}
|
|
440
|
-
for (const entry of entries) {
|
|
441
|
-
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
442
|
-
const full = join(d, entry.name);
|
|
443
|
-
if (entry.isDirectory()) walk(full);
|
|
444
|
-
else {
|
|
445
|
-
const ext = `.${entry.name.split(".").pop()}`;
|
|
446
|
-
if (extensions.has(ext)) results.push(full);
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
walk(dir);
|
|
451
|
-
return results;
|
|
452
|
-
}
|
|
453
|
-
function findHashCommentFiles(dir) {
|
|
454
|
-
const results = [];
|
|
455
|
-
const extensions = new Set([
|
|
456
|
-
".sh",
|
|
457
|
-
".mk",
|
|
458
|
-
".yaml",
|
|
459
|
-
".yml",
|
|
460
|
-
".py"
|
|
461
|
-
]);
|
|
462
|
-
function walk(d) {
|
|
463
|
-
let entries;
|
|
464
|
-
try {
|
|
465
|
-
entries = readdirSync(d, { withFileTypes: true });
|
|
466
|
-
} catch {
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
469
|
-
for (const entry of entries) {
|
|
470
|
-
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
471
|
-
const full = join(d, entry.name);
|
|
472
|
-
if (entry.isDirectory()) walk(full);
|
|
473
|
-
else {
|
|
474
|
-
const ext = `.${entry.name.split(".").pop()}`;
|
|
475
|
-
if (extensions.has(ext) || entry.name === "Makefile") results.push(full);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
walk(dir);
|
|
480
|
-
return results;
|
|
138
|
+
if (!options.skipInstall) {
|
|
139
|
+
console.log("\nInstalling dependencies...");
|
|
140
|
+
await spawn("pnpm", ["install"], {
|
|
141
|
+
cwd: targetDir,
|
|
142
|
+
stdio: "inherit"
|
|
143
|
+
});
|
|
144
|
+
if (scaffoldCommitted) await spawn("git", [
|
|
145
|
+
"add",
|
|
146
|
+
"--",
|
|
147
|
+
"pnpm-lock.yaml"
|
|
148
|
+
], { cwd: targetDir }).then(() => spawn("git", [
|
|
149
|
+
"commit",
|
|
150
|
+
"--amend",
|
|
151
|
+
"--no-edit",
|
|
152
|
+
"--quiet"
|
|
153
|
+
], { cwd: targetDir })).catch(() => {});
|
|
154
|
+
} else console.log("Skipped pnpm install (--skip-install).");
|
|
155
|
+
console.log(`\n✅ Project created at ${targetDir}`);
|
|
156
|
+
console.log("\nNext steps:");
|
|
157
|
+
if (!options.into) console.log(` cd ${basename(targetDir)}`);
|
|
158
|
+
if (options.skipInstall) console.log(" pnpm install");
|
|
159
|
+
if (options.into || options.skipGit) console.log(" git add -A && git commit -m \"feat: scaffold from multiplatform.one (mpo init)\"");
|
|
160
|
+
console.log(" pnpm dev # web + native dev server");
|
|
161
|
+
if (services.includes("frappe")) console.log(" make frappe/dev # Frappe bench (see apps/frappe)");
|
|
162
|
+
console.log("\nLater: `mpo update` merges monorepo changes into the project without committing;");
|
|
163
|
+
console.log("`mpo add-app <name>` adds another product app.");
|
|
481
164
|
}
|
|
482
165
|
|
|
483
166
|
//#endregion
|
|
484
|
-
export { init,
|
|
167
|
+
export { SINGLETON_APPS, init, validateServices };
|