@rollipop/init 0.1.0-alpha.14 → 0.1.0-alpha.15
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/CHANGELOG.md +6 -0
- package/dist/index.js +118 -84
- package/package.json +3 -5
package/CHANGELOG.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,71 +1,86 @@
|
|
|
1
|
+
import pc from "picocolors";
|
|
1
2
|
import fs from "node:fs";
|
|
2
3
|
import path from "node:path";
|
|
3
|
-
import
|
|
4
|
-
import pc from "picocolors";
|
|
5
|
-
import whichPm from "which-pm";
|
|
6
|
-
import xcode from "xcode";
|
|
4
|
+
import { Visitor, parseSync } from "oxc-parser";
|
|
7
5
|
|
|
8
|
-
//#region src/
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
6
|
+
//#region src/init.ts
|
|
7
|
+
const RN_CONFIG_FILE = "react-native.config.js";
|
|
8
|
+
const COMMANDS_REQUIRE = "require('rollipop/commands')";
|
|
9
|
+
function setupReactNativeConfig(cwd) {
|
|
10
|
+
const configPath = path.join(cwd, RN_CONFIG_FILE);
|
|
11
|
+
if (!fs.existsSync(configPath)) {
|
|
12
|
+
fs.writeFileSync(configPath, `module.exports = {\n commands: ${COMMANDS_REQUIRE},\n};\n`);
|
|
13
|
+
return "created";
|
|
14
|
+
}
|
|
15
|
+
const content = fs.readFileSync(configPath, "utf8");
|
|
16
|
+
const result = analyzeConfig(content);
|
|
17
|
+
switch (result.status) {
|
|
18
|
+
case "already-configured": return "already-configured";
|
|
19
|
+
case "has-other-commands": throw new Error(`'commands' property already exists with a different value`);
|
|
20
|
+
case "not-object-export": return "manual-required";
|
|
21
|
+
case "injectable": {
|
|
22
|
+
const updated = applyInjection(content, result.insertOffset, result.needsComma);
|
|
23
|
+
fs.writeFileSync(configPath, updated);
|
|
24
|
+
return "updated";
|
|
25
|
+
}
|
|
17
26
|
}
|
|
18
|
-
};
|
|
19
|
-
async function findAppBuildGradle(cwd) {
|
|
20
|
-
const files = await glob("**/android/app/build.gradle", { cwd });
|
|
21
|
-
if (files.length === 0) throw new Error("No Android build.gradle found");
|
|
22
|
-
if (files.length > 1) throw new Error("Multiple Android build.gradle found:\n" + files.join("\n"));
|
|
23
|
-
return path.join(cwd, files[0]);
|
|
24
27
|
}
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
28
|
+
function analyzeConfig(content) {
|
|
29
|
+
const { program } = parseSync("react-native.config.js", content);
|
|
30
|
+
let moduleExportsAssignment = null;
|
|
31
|
+
new Visitor({ AssignmentExpression(node) {
|
|
32
|
+
if (isModuleExports(node)) moduleExportsAssignment = node;
|
|
33
|
+
} }).visit(program);
|
|
34
|
+
if (!moduleExportsAssignment) return { status: "not-object-export" };
|
|
35
|
+
const assignment = moduleExportsAssignment;
|
|
36
|
+
if (assignment.right.type !== "ObjectExpression") return { status: "not-object-export" };
|
|
37
|
+
const objectExpr = assignment.right;
|
|
38
|
+
const commandsProp = findCommandsProperty(objectExpr);
|
|
39
|
+
if (commandsProp) {
|
|
40
|
+
if (isRollipopCommandsRequire(commandsProp, content)) return { status: "already-configured" };
|
|
41
|
+
return { status: "has-other-commands" };
|
|
42
|
+
}
|
|
43
|
+
const properties = objectExpr.properties;
|
|
44
|
+
const lastProp = properties[properties.length - 1];
|
|
45
|
+
const closingBrace = objectExpr.end - 1;
|
|
46
|
+
if (lastProp) {
|
|
47
|
+
const commaIndex = content.substring(lastProp.end, closingBrace).indexOf(",");
|
|
48
|
+
if (commaIndex !== -1) return {
|
|
49
|
+
status: "injectable",
|
|
50
|
+
insertOffset: lastProp.end + commaIndex + 1,
|
|
51
|
+
needsComma: false
|
|
52
|
+
};
|
|
53
|
+
return {
|
|
54
|
+
status: "injectable",
|
|
55
|
+
insertOffset: lastProp.end,
|
|
56
|
+
needsComma: true
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
status: "injectable",
|
|
61
|
+
insertOffset: closingBrace,
|
|
62
|
+
needsComma: false
|
|
63
|
+
};
|
|
35
64
|
}
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
cwd: basePath,
|
|
40
|
-
ignore: ["Pods/**"]
|
|
41
|
-
});
|
|
42
|
-
if (files.length === 0) throw new Error("No Xcode project found");
|
|
43
|
-
if (files.length > 1) throw new Error("Multiple Xcode projects found:\n" + files.join("\n"));
|
|
44
|
-
return path.join(basePath, files[0]);
|
|
65
|
+
function isModuleExports(node) {
|
|
66
|
+
const left = node.left;
|
|
67
|
+
return left.type === "MemberExpression" && left.object.type === "Identifier" && left.object.name === "module" && left.property.type === "Identifier" && left.property.name === "exports";
|
|
45
68
|
}
|
|
46
|
-
function
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const index = originShellScriptLines.findIndex((line) => line.trim().startsWith("set -"));
|
|
54
|
-
let newShellScript = "";
|
|
55
|
-
if (index === -1) newShellScript = `${EXPORT_CLI_PATH}\n${originShellScript}`;
|
|
56
|
-
else newShellScript = [
|
|
57
|
-
...originShellScriptLines.slice(0, index + 1),
|
|
58
|
-
EXPORT_CLI_PATH,
|
|
59
|
-
...originShellScriptLines.slice(index + 1)
|
|
60
|
-
].join("\n");
|
|
61
|
-
buildPhase[1].shellScript = JSON.stringify(newShellScript);
|
|
62
|
-
fs.writeFileSync(project.filepath, project.writeSync());
|
|
69
|
+
function findCommandsProperty(obj) {
|
|
70
|
+
for (const prop of obj.properties) {
|
|
71
|
+
if (prop.type !== "Property") continue;
|
|
72
|
+
const p = prop;
|
|
73
|
+
if (p.key.type === "Identifier" && p.key.name === "commands" || p.key.type === "Literal" && p.key.value === "commands") return p;
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
63
76
|
}
|
|
64
|
-
|
|
65
|
-
|
|
77
|
+
function isRollipopCommandsRequire(prop, content) {
|
|
78
|
+
return content.substring(prop.value.start, prop.value.end).includes("rollipop/commands");
|
|
66
79
|
}
|
|
67
|
-
|
|
68
|
-
|
|
80
|
+
function applyInjection(content, insertOffset, needsComma) {
|
|
81
|
+
const before = content.substring(0, insertOffset);
|
|
82
|
+
const after = content.substring(insertOffset);
|
|
83
|
+
return `${before}${needsComma ? "," : ""}\n commands: ${COMMANDS_REQUIRE},${after}`;
|
|
69
84
|
}
|
|
70
85
|
function setupPackage(cwd) {
|
|
71
86
|
const packageJsonPath = path.join(cwd, "package.json");
|
|
@@ -74,47 +89,66 @@ function setupPackage(cwd) {
|
|
|
74
89
|
...packageJson.devDependencies,
|
|
75
90
|
rollipop: "latest"
|
|
76
91
|
};
|
|
77
|
-
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
92
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n");
|
|
78
93
|
}
|
|
94
|
+
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/index.ts
|
|
97
|
+
const logger = {
|
|
98
|
+
success(message) {
|
|
99
|
+
console.log(pc.green("✓"), message);
|
|
100
|
+
},
|
|
101
|
+
info(message) {
|
|
102
|
+
console.log(pc.cyan("ℹ"), message);
|
|
103
|
+
},
|
|
104
|
+
warn(message) {
|
|
105
|
+
console.log(pc.yellow("⚠"), message);
|
|
106
|
+
},
|
|
107
|
+
error(message, reason) {
|
|
108
|
+
console.error(pc.red("✗"), `${message}: ${reason instanceof Error ? reason.message : String(reason)}`);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
console.log(`\n${pc.bold(pc.cyan("rollipop"))} ${pc.dim("— Modern build toolkit for React Native")}\n`);
|
|
79
112
|
const cwd = process.cwd();
|
|
80
113
|
let failed = false;
|
|
114
|
+
let hasWarning = false;
|
|
81
115
|
try {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
116
|
+
switch (setupReactNativeConfig(cwd)) {
|
|
117
|
+
case "created":
|
|
118
|
+
logger.success(`Created ${pc.bold("react-native.config.js")}`);
|
|
119
|
+
break;
|
|
120
|
+
case "updated":
|
|
121
|
+
logger.success(`Updated ${pc.bold("react-native.config.js")}`);
|
|
122
|
+
break;
|
|
123
|
+
case "already-configured":
|
|
124
|
+
logger.info(`${pc.bold("react-native.config.js")} already configured`);
|
|
125
|
+
break;
|
|
126
|
+
case "manual-required":
|
|
127
|
+
hasWarning = true;
|
|
128
|
+
logger.warn(`Could not auto-configure: ${pc.bold("module.exports")} is not a plain object`);
|
|
129
|
+
console.log(` ${pc.dim("Add the following to your")} ${pc.bold("react-native.config.js")}${pc.dim(":")}\n ${pc.green("commands: require('rollipop/commands'")})`);
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
91
132
|
} catch (error) {
|
|
92
|
-
logger.error("
|
|
133
|
+
logger.error("React Native CLI setup failed", error);
|
|
93
134
|
failed ||= true;
|
|
94
135
|
}
|
|
95
|
-
|
|
136
|
+
if (hasWarning || failed) console.log();
|
|
96
137
|
try {
|
|
97
|
-
packageManager = (await whichPm(cwd))?.name ?? null;
|
|
98
138
|
setupPackage(cwd);
|
|
99
|
-
logger.success("
|
|
139
|
+
logger.success(`Added ${pc.bold("rollipop")} to ${pc.bold("devDependencies")}`);
|
|
100
140
|
} catch (error) {
|
|
101
141
|
logger.error("Package setup failed", error);
|
|
102
142
|
failed ||= true;
|
|
103
143
|
}
|
|
104
144
|
if (failed) {
|
|
105
|
-
console.
|
|
106
|
-
Failed to setup project automatically
|
|
107
|
-
|
|
108
|
-
Please follow the manual setup guide: ${pc.underline("https://rollipop.dev/docs/get-started/quick-start")}`);
|
|
145
|
+
console.log("\n" + pc.red("Setup failed.") + ` Follow the manual guide: ${pc.underline(pc.cyan("https://rollipop.dev/docs/get-started/quick-start"))}`);
|
|
109
146
|
process.exit(1);
|
|
110
|
-
} else {
|
|
111
|
-
logger.success("Project setup completed");
|
|
112
|
-
if (packageManager == null) console.log("No package manager found, please install dependencies manually");
|
|
113
|
-
else {
|
|
114
|
-
const command = pc.bold(`${packageManager} install`);
|
|
115
|
-
console.log(`Run ${command} to install dependencies`);
|
|
116
|
-
}
|
|
117
147
|
}
|
|
148
|
+
logger.success(pc.bold("Setup completed!"));
|
|
149
|
+
console.log();
|
|
150
|
+
console.log(` ${pc.dim("Install dependencies manually to finish setup.")}`);
|
|
151
|
+
console.log(` ${pc.dim("See")} ${pc.underline(pc.cyan("https://rollipop.dev"))} ${pc.dim("for configuration details.")}`);
|
|
118
152
|
|
|
119
153
|
//#endregion
|
|
120
154
|
export { };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rollipop/init",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.15",
|
|
4
4
|
"homepage": "https://github.com/leegeunhyeok/rollipop#readme",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/leegeunhyeok/rollipop/issues"
|
|
@@ -26,10 +26,8 @@
|
|
|
26
26
|
"build": "tsdown"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"
|
|
30
|
-
"picocolors": "^1.1.1"
|
|
31
|
-
"which-pm": "^3.0.1",
|
|
32
|
-
"xcode": "^3.0.1"
|
|
29
|
+
"oxc-parser": "^0.121.0",
|
|
30
|
+
"picocolors": "^1.1.1"
|
|
33
31
|
},
|
|
34
32
|
"devDependencies": {
|
|
35
33
|
"@oxc-node/core": "^0.0.35",
|