@rollipop/init 1.0.0-beta.1 → 1.0.0-beta.3
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 +12 -0
- package/dist/index.js +163 -15
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1 +1,13 @@
|
|
|
1
1
|
# @rollipop/init
|
|
2
|
+
|
|
3
|
+
Run from a React Native or Expo project root:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
yarn dlx @rollipop/init@beta
|
|
7
|
+
yarn install
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
- Bare projects: logs `bare project detected`, adds the matching `rollipop` version, and configures `react-native.config.js` and `ios/.xcode.env` for Xcode bundle builds. The Xcode setup resolves local or hoisted `node_modules` without running Node before `.xcode.env.local` loads, preserves existing Node settings, and reports custom `CLI_PATH` configurations for manual setup. Projects without `ios` are skipped.
|
|
11
|
+
- Expo projects: logs `expo project detected`, adds matching `rollipop` and `@rollipop/expo` versions, and registers the config plugin in `app.json` or `app.config.json` while preserving existing plugins. Dynamic JS/TS configuration is left unchanged with manual setup instructions.
|
|
12
|
+
|
|
13
|
+
Expo detection uses the `expo` dependency in `package.json`, including prebuilt projects with native directories. See [Quick Start](https://rollipop.dev/docs/get-started/quick-start) or the [Expo guide](https://rollipop.dev/docs/experimental/expo) for setup and limitations.
|
package/dist/index.js
CHANGED
|
@@ -2,12 +2,93 @@ import pc from "picocolors";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { Visitor, parseSync } from "oxc-parser";
|
|
5
|
-
//#region package.json
|
|
6
|
-
var version = "1.0.0-beta.1";
|
|
7
5
|
//#endregion
|
|
8
6
|
//#region src/init.ts
|
|
7
|
+
const { version } = {
|
|
8
|
+
name: "@rollipop/init",
|
|
9
|
+
version: "1.0.0-beta.3",
|
|
10
|
+
homepage: "https://github.com/rollipop-dev/rollipop#readme",
|
|
11
|
+
bugs: { "url": "https://github.com/rollipop-dev/rollipop/issues" },
|
|
12
|
+
license: "MIT",
|
|
13
|
+
author: "leegeunhyeok <dev.ghlee@gmail.com> (https://github.com/leegeunhyeok)",
|
|
14
|
+
repository: {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/rollipop-dev/rollipop.git",
|
|
17
|
+
"directory": "packages/core"
|
|
18
|
+
},
|
|
19
|
+
bin: "./bin/index.js",
|
|
20
|
+
files: ["bin", "dist"],
|
|
21
|
+
type: "module",
|
|
22
|
+
scripts: {
|
|
23
|
+
"execute": "node --import @oxc-node/core/register src/index.ts",
|
|
24
|
+
"prepack": "yarn build",
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
26
|
+
"test": "vp test --run --passWithNoTests",
|
|
27
|
+
"build": "vp pack"
|
|
28
|
+
},
|
|
29
|
+
dependencies: {
|
|
30
|
+
"oxc-parser": "0.151.0",
|
|
31
|
+
"picocolors": "1.1.1"
|
|
32
|
+
},
|
|
33
|
+
devDependencies: {
|
|
34
|
+
"@oxc-node/core": "^0.1.3",
|
|
35
|
+
"typescript": "catalog:",
|
|
36
|
+
"vite-plus": "catalog:vp"
|
|
37
|
+
},
|
|
38
|
+
installConfig: { "hoistingLimits": "none" }
|
|
39
|
+
};
|
|
9
40
|
const RN_CONFIG_FILE = "react-native.config.js";
|
|
10
41
|
const COMMANDS_REQUIRE = "require('rollipop/commands')";
|
|
42
|
+
const XCODE_CLI_PATH = "export CLI_PATH=\"$PROJECT_DIR/../node_modules/rollipop/bin/index.js\"";
|
|
43
|
+
const XCODE_ENV_SETUP = [
|
|
44
|
+
"# Resolve Rollipop without running Node before .xcode.env.local is loaded.",
|
|
45
|
+
"if [ -z \"${CLI_PATH:-}\" ]; then",
|
|
46
|
+
" ROLLIPOP_CLI_ROOT=\"$(cd \"$PROJECT_DIR/..\" && pwd)\"",
|
|
47
|
+
" while [ ! -f \"$ROLLIPOP_CLI_ROOT/node_modules/rollipop/bin/index.js\" ] && [ \"$ROLLIPOP_CLI_ROOT\" != / ]; do",
|
|
48
|
+
" ROLLIPOP_CLI_ROOT=\"${ROLLIPOP_CLI_ROOT%/*}\"",
|
|
49
|
+
" ROLLIPOP_CLI_ROOT=\"${ROLLIPOP_CLI_ROOT:-/}\"",
|
|
50
|
+
" done",
|
|
51
|
+
" export CLI_PATH=\"$ROLLIPOP_CLI_ROOT/node_modules/rollipop/bin/index.js\"",
|
|
52
|
+
" unset ROLLIPOP_CLI_ROOT",
|
|
53
|
+
"fi"
|
|
54
|
+
].join("\n");
|
|
55
|
+
function detectProject(cwd) {
|
|
56
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(cwd, "package.json"), "utf8"));
|
|
57
|
+
return packageJson.dependencies?.expo || packageJson.devDependencies?.expo ? "expo" : "bare";
|
|
58
|
+
}
|
|
59
|
+
function setupExpoConfig(cwd) {
|
|
60
|
+
const dynamicConfig = [
|
|
61
|
+
"ts",
|
|
62
|
+
"mts",
|
|
63
|
+
"cts",
|
|
64
|
+
"mjs",
|
|
65
|
+
"cjs",
|
|
66
|
+
"js"
|
|
67
|
+
].map((extension) => `app.config.${extension}`).find((filename) => fs.existsSync(path.join(cwd, filename)));
|
|
68
|
+
if (dynamicConfig) return {
|
|
69
|
+
status: "manual-required",
|
|
70
|
+
filename: dynamicConfig
|
|
71
|
+
};
|
|
72
|
+
const filename = ["app.config.json", "app.json"].find((name) => fs.existsSync(path.join(cwd, name))) ?? "app.json";
|
|
73
|
+
const configPath = path.join(cwd, filename);
|
|
74
|
+
const exists = fs.existsSync(configPath);
|
|
75
|
+
const root = exists ? JSON.parse(fs.readFileSync(configPath, "utf8")) : { expo: {} };
|
|
76
|
+
if (!root || typeof root !== "object" || Array.isArray(root)) throw new Error(`${filename} must contain an Expo configuration object`);
|
|
77
|
+
const config = "expo" in root ? root.expo : root;
|
|
78
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) throw new Error(`${filename}: 'expo' must be an object`);
|
|
79
|
+
const plugins = config.plugins ?? [];
|
|
80
|
+
if (!Array.isArray(plugins)) throw new Error(`${filename}: 'plugins' must be an array`);
|
|
81
|
+
if (plugins.some((plugin) => (Array.isArray(plugin) ? plugin[0] : plugin) === "@rollipop/expo")) return {
|
|
82
|
+
status: "already-configured",
|
|
83
|
+
filename
|
|
84
|
+
};
|
|
85
|
+
config.plugins = [...plugins, "@rollipop/expo"];
|
|
86
|
+
fs.writeFileSync(configPath, JSON.stringify(root, null, 2) + "\n");
|
|
87
|
+
return {
|
|
88
|
+
status: exists ? "updated" : "created",
|
|
89
|
+
filename
|
|
90
|
+
};
|
|
91
|
+
}
|
|
11
92
|
function setupReactNativeConfig(cwd) {
|
|
12
93
|
const configPath = path.join(cwd, RN_CONFIG_FILE);
|
|
13
94
|
if (!fs.existsSync(configPath)) {
|
|
@@ -27,6 +108,27 @@ function setupReactNativeConfig(cwd) {
|
|
|
27
108
|
}
|
|
28
109
|
}
|
|
29
110
|
}
|
|
111
|
+
function setupXcodeEnv(cwd) {
|
|
112
|
+
const iosDir = path.join(cwd, "ios");
|
|
113
|
+
if (!fs.existsSync(iosDir)) return "skipped";
|
|
114
|
+
const filename = path.join(iosDir, ".xcode.env");
|
|
115
|
+
const exists = fs.existsSync(filename);
|
|
116
|
+
const content = exists ? fs.readFileSync(filename, "utf8") : "";
|
|
117
|
+
const localFilename = `${filename}.local`;
|
|
118
|
+
const local = fs.existsSync(localFilename) ? fs.readFileSync(localFilename, "utf8") : "";
|
|
119
|
+
const hasCliPath = (source) => source.split(/\r?\n/).some((line) => !line.trimStart().startsWith("#") && /\bCLI_PATH\b/.test(line));
|
|
120
|
+
const hasProjectOverride = fs.readdirSync(iosDir).some((entry) => {
|
|
121
|
+
const projectFile = path.join(iosDir, entry, "project.pbxproj");
|
|
122
|
+
return entry.endsWith(".xcodeproj") && fs.existsSync(projectFile) && /\bCLI_PATH\b/.test(fs.readFileSync(projectFile, "utf8"));
|
|
123
|
+
});
|
|
124
|
+
if (hasCliPath(local) || hasProjectOverride) return "manual-required";
|
|
125
|
+
const configured = content.includes(XCODE_ENV_SETUP) || content.split(/\r?\n/).includes(XCODE_CLI_PATH);
|
|
126
|
+
if (hasCliPath(content.replace(XCODE_ENV_SETUP, "").replace(XCODE_CLI_PATH, ""))) return "manual-required";
|
|
127
|
+
if (configured) return "already-configured";
|
|
128
|
+
const separator = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
129
|
+
fs.writeFileSync(filename, `${content}${separator}${XCODE_ENV_SETUP}\n`);
|
|
130
|
+
return exists ? "updated" : "created";
|
|
131
|
+
}
|
|
30
132
|
function analyzeConfig(content) {
|
|
31
133
|
const { program, comments } = parseSync("react-native.config.js", content);
|
|
32
134
|
let moduleExportsAssignment = null;
|
|
@@ -90,11 +192,16 @@ function applyInjection(content, insertOffset, needsComma) {
|
|
|
90
192
|
function setupPackage(cwd) {
|
|
91
193
|
const packageJsonPath = path.join(cwd, "package.json");
|
|
92
194
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
195
|
+
const dependencies = {
|
|
196
|
+
rollipop: version,
|
|
197
|
+
...detectProject(cwd) === "expo" ? { "@rollipop/expo": version } : {}
|
|
198
|
+
};
|
|
93
199
|
packageJson.devDependencies = {
|
|
94
200
|
...packageJson.devDependencies,
|
|
95
|
-
|
|
201
|
+
...dependencies
|
|
96
202
|
};
|
|
97
203
|
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n");
|
|
204
|
+
return Object.keys(dependencies);
|
|
98
205
|
}
|
|
99
206
|
//#endregion
|
|
100
207
|
//#region src/index.ts
|
|
@@ -114,43 +221,84 @@ const logger = {
|
|
|
114
221
|
};
|
|
115
222
|
console.log(`\n${pc.bold(pc.cyan("rollipop"))} ${pc.dim("— Modern build toolkit for React Native")}\n`);
|
|
116
223
|
const cwd = process.cwd();
|
|
224
|
+
let projectType;
|
|
225
|
+
try {
|
|
226
|
+
projectType = detectProject(cwd);
|
|
227
|
+
} catch (error) {
|
|
228
|
+
logger.error("Project detection failed", error);
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
logger.info(`${projectType} project detected`);
|
|
117
232
|
let failed = false;
|
|
118
233
|
let hasWarning = false;
|
|
234
|
+
const guideUrl = projectType === "expo" ? "https://rollipop.dev/docs/experimental/expo" : "https://rollipop.dev/docs/get-started/quick-start";
|
|
119
235
|
try {
|
|
120
|
-
|
|
236
|
+
const { status, filename } = projectType === "expo" ? setupExpoConfig(cwd) : {
|
|
237
|
+
status: setupReactNativeConfig(cwd),
|
|
238
|
+
filename: "react-native.config.js"
|
|
239
|
+
};
|
|
240
|
+
switch (status) {
|
|
121
241
|
case "created":
|
|
122
|
-
logger.success(`Created ${pc.bold(
|
|
242
|
+
logger.success(`Created ${pc.bold(filename)}`);
|
|
123
243
|
break;
|
|
124
244
|
case "updated":
|
|
125
|
-
logger.success(`Updated ${pc.bold(
|
|
245
|
+
logger.success(`Updated ${pc.bold(filename)}`);
|
|
126
246
|
break;
|
|
127
247
|
case "already-configured":
|
|
128
|
-
logger.info(`${pc.bold(
|
|
248
|
+
logger.info(`${pc.bold(filename)} already configured`);
|
|
249
|
+
break;
|
|
250
|
+
case "manual-required":
|
|
251
|
+
hasWarning = true;
|
|
252
|
+
if (projectType === "expo") {
|
|
253
|
+
logger.warn(`Could not auto-configure dynamic Expo config: ${pc.bold(filename)}`);
|
|
254
|
+
console.log(` Add ${pc.green("'@rollipop/expo'")} to the exported config's ${pc.bold("plugins")} array, keeping existing entries.`);
|
|
255
|
+
} else {
|
|
256
|
+
logger.warn(`Could not auto-configure: ${pc.bold("module.exports")} is not a plain object`);
|
|
257
|
+
console.log(` ${pc.dim("Add the following to your")} ${pc.bold(filename)}${pc.dim(":")}\n ${pc.green("commands: require('rollipop/commands')")}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
} catch (error) {
|
|
261
|
+
logger.error(projectType === "expo" ? "Expo setup failed" : "React Native CLI setup failed", error);
|
|
262
|
+
failed ||= true;
|
|
263
|
+
}
|
|
264
|
+
if (projectType === "bare") try {
|
|
265
|
+
const status = setupXcodeEnv(cwd);
|
|
266
|
+
switch (status) {
|
|
267
|
+
case "created":
|
|
268
|
+
case "updated":
|
|
269
|
+
logger.success(`${status === "created" ? "Created" : "Updated"} ${pc.bold("ios/.xcode.env")}`);
|
|
270
|
+
break;
|
|
271
|
+
case "already-configured":
|
|
272
|
+
logger.info(`${pc.bold("ios/.xcode.env")} already configured`);
|
|
273
|
+
break;
|
|
274
|
+
case "skipped":
|
|
275
|
+
logger.info("No ios directory; skipped Xcode bundle setup");
|
|
129
276
|
break;
|
|
130
277
|
case "manual-required":
|
|
131
278
|
hasWarning = true;
|
|
132
|
-
logger.warn(
|
|
133
|
-
console.log(`
|
|
279
|
+
logger.warn("Existing CLI_PATH configuration requires manual Xcode bundle setup");
|
|
280
|
+
console.log(` Point ${pc.bold("CLI_PATH")} to ${pc.green("rollipop/bin/index.js")} in your Xcode environment or bundle phase, keeping your Node setup.`);
|
|
134
281
|
}
|
|
135
282
|
} catch (error) {
|
|
136
|
-
logger.error("
|
|
283
|
+
logger.error("Xcode bundle setup failed", error);
|
|
137
284
|
failed ||= true;
|
|
138
285
|
}
|
|
139
286
|
if (hasWarning || failed) console.log();
|
|
140
287
|
try {
|
|
141
|
-
setupPackage(cwd);
|
|
142
|
-
logger.success(`Added ${pc.bold("
|
|
288
|
+
const dependencies = setupPackage(cwd);
|
|
289
|
+
logger.success(`Added ${dependencies.map((dependency) => pc.bold(dependency)).join(", ")} to ${pc.bold("devDependencies")}`);
|
|
143
290
|
} catch (error) {
|
|
144
291
|
logger.error("Package setup failed", error);
|
|
145
292
|
failed ||= true;
|
|
146
293
|
}
|
|
147
294
|
if (failed) {
|
|
148
|
-
console.log("\n" + pc.red("Setup failed.") + ` Follow the manual guide: ${pc.underline(pc.cyan(
|
|
295
|
+
console.log("\n" + pc.red("Setup failed.") + ` Follow the manual guide: ${pc.underline(pc.cyan(guideUrl))}`);
|
|
149
296
|
process.exit(1);
|
|
150
297
|
}
|
|
151
|
-
logger.
|
|
298
|
+
if (hasWarning) logger.warn("Manual configuration is required to finish setup.");
|
|
299
|
+
else logger.success(pc.bold("Setup completed!"));
|
|
152
300
|
console.log();
|
|
153
301
|
console.log(` ${pc.dim("Install dependencies manually to finish setup.")}`);
|
|
154
|
-
console.log(` ${pc.dim("See")} ${pc.underline(pc.cyan(
|
|
302
|
+
console.log(` ${pc.dim("See")} ${pc.underline(pc.cyan(guideUrl))} ${pc.dim("for configuration details.")}`);
|
|
155
303
|
//#endregion
|
|
156
304
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rollipop/init",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.3",
|
|
4
4
|
"homepage": "https://github.com/rollipop-dev/rollipop#readme",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/rollipop-dev/rollipop/issues"
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@oxc-node/core": "^0.1.3",
|
|
34
34
|
"typescript": "7.0.2",
|
|
35
|
-
"vite-plus": "
|
|
35
|
+
"vite-plus": "1.3.0"
|
|
36
36
|
},
|
|
37
37
|
"installConfig": {
|
|
38
38
|
"hoistingLimits": "none"
|