@rollipop/init 0.1.0-dev.20260118071417 → 0.1.0-dev.20260424074146

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/index.js +120 -89
  3. package/package.json +21 -24
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @rollipop/init
2
2
 
3
+ ## 0.1.0-alpha.17
4
+
5
+ ## 0.1.0-alpha.16
6
+
7
+ ## 0.1.0-alpha.15
8
+
9
+ ### Patch Changes
10
+
11
+ - 922142f: update `commands` instead of patching script
12
+
13
+ ## 0.1.0-alpha.14
14
+
15
+ ## 0.1.0-alpha.13
16
+
17
+ ## 0.1.0-alpha.12
18
+
19
+ ## 0.1.0-alpha.11
20
+
21
+ ## 0.1.0-alpha.10
22
+
3
23
  ## 0.1.0-alpha.9
4
24
 
5
25
  ## 0.1.0-alpha.8
package/dist/index.js CHANGED
@@ -1,120 +1,151 @@
1
+ import pc from "picocolors";
1
2
  import fs from "node:fs";
2
3
  import path from "node:path";
3
- import glob from "fast-glob";
4
- import pc from "picocolors";
5
- import whichPm from "which-pm";
6
- import xcode from "xcode";
7
-
8
- //#region src/index.ts
9
- const TARGET_BUILD_PHASE_NAME = "Bundle React Native code and images";
10
- const EXPORT_CLI_PATH = "export CLI_PATH=\"$PROJECT_DIR/node_modules/rollipop/bin/index.js\"";
11
- const logger = {
12
- success(message) {
13
- console.log(pc.green(""), message);
14
- },
15
- error(message, reason) {
16
- console.error(pc.red(""), `${message}: ${reason instanceof Error ? reason.message : String(reason)}`);
4
+ import { Visitor, parseSync } from "oxc-parser";
5
+ //#region src/init.ts
6
+ const RN_CONFIG_FILE = "react-native.config.js";
7
+ const COMMANDS_REQUIRE = "require('rollipop/commands')";
8
+ function setupReactNativeConfig(cwd) {
9
+ const configPath = path.join(cwd, RN_CONFIG_FILE);
10
+ if (!fs.existsSync(configPath)) {
11
+ fs.writeFileSync(configPath, `module.exports = {\n commands: ${COMMANDS_REQUIRE},\n};\n`);
12
+ return "created";
13
+ }
14
+ const content = fs.readFileSync(configPath, "utf8");
15
+ const result = analyzeConfig(content);
16
+ switch (result.status) {
17
+ case "already-configured": return "already-configured";
18
+ case "has-other-commands": throw new Error(`'commands' property already exists with a different value`);
19
+ case "not-object-export": return "manual-required";
20
+ case "injectable": {
21
+ const updated = applyInjection(content, result.insertOffset, result.needsComma);
22
+ fs.writeFileSync(configPath, updated);
23
+ return "updated";
24
+ }
17
25
  }
18
- };
19
- async function findAppBuildGradle(cwd$1) {
20
- const files = await glob("**/android/app/build.gradle", { cwd: cwd$1 });
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$1, files[0]);
24
26
  }
25
- async function updateGradleCLIPath(appBuildGradlePath) {
26
- const appBuildGradleLines = fs.readFileSync(appBuildGradlePath, "utf8").split("\n");
27
- if (appBuildGradleLines.find((line) => line.trim().startsWith("cliFile ="))) throw new Error(`'cliFile' already configured`);
28
- const index = appBuildGradleLines.findIndex((line) => line.trim().startsWith("react {"));
29
- if (index === -1) throw new Error(`'react' configuration block not found in build.gradle`);
30
- fs.writeFileSync(appBuildGradlePath, [
31
- ...appBuildGradleLines.slice(0, index + 1),
32
- " cliFile = file(\"../../node_modules/rollipop/bin/index.js\")",
33
- ...appBuildGradleLines.slice(index + 1)
34
- ].join("\n"));
27
+ function analyzeConfig(content) {
28
+ const { program } = parseSync("react-native.config.js", content);
29
+ let moduleExportsAssignment = null;
30
+ new Visitor({ AssignmentExpression(node) {
31
+ if (isModuleExports(node)) moduleExportsAssignment = node;
32
+ } }).visit(program);
33
+ if (!moduleExportsAssignment) return { status: "not-object-export" };
34
+ const assignment = moduleExportsAssignment;
35
+ if (assignment.right.type !== "ObjectExpression") return { status: "not-object-export" };
36
+ const objectExpr = assignment.right;
37
+ const commandsProp = findCommandsProperty(objectExpr);
38
+ if (commandsProp) {
39
+ if (isRollipopCommandsRequire(commandsProp, content)) return { status: "already-configured" };
40
+ return { status: "has-other-commands" };
41
+ }
42
+ const properties = objectExpr.properties;
43
+ const lastProp = properties[properties.length - 1];
44
+ const closingBrace = objectExpr.end - 1;
45
+ if (lastProp) {
46
+ const commaIndex = content.substring(lastProp.end, closingBrace).indexOf(",");
47
+ if (commaIndex !== -1) return {
48
+ status: "injectable",
49
+ insertOffset: lastProp.end + commaIndex + 1,
50
+ needsComma: false
51
+ };
52
+ return {
53
+ status: "injectable",
54
+ insertOffset: lastProp.end,
55
+ needsComma: true
56
+ };
57
+ }
58
+ return {
59
+ status: "injectable",
60
+ insertOffset: closingBrace,
61
+ needsComma: false
62
+ };
35
63
  }
36
- async function findXCodeProject(cwd$1) {
37
- const basePath = path.join(cwd$1, "ios");
38
- const files = await glob("**/*.xcodeproj/project.pbxproj", {
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]);
64
+ function isModuleExports(node) {
65
+ const left = node.left;
66
+ return left.type === "MemberExpression" && left.object.type === "Identifier" && left.object.name === "module" && left.property.type === "Identifier" && left.property.name === "exports";
45
67
  }
46
- function updateXCodeCLIPath(xcodeProjectPath) {
47
- const project = xcode.project(xcodeProjectPath).parseSync();
48
- const buildPhase = Object.entries(project.hash?.project?.objects?.PBXShellScriptBuildPhase ?? {}).find(([_key, phase]) => phase.name.includes(TARGET_BUILD_PHASE_NAME));
49
- if (buildPhase == null) throw new Error(`'${TARGET_BUILD_PHASE_NAME}' build phase not found`);
50
- const originShellScript = JSON.parse(buildPhase[1].shellScript);
51
- if (originShellScript.includes("CLI_PATH=")) throw new Error(`'CLI_PATH' environment variable already configured`);
52
- const originShellScriptLines = originShellScript.split("\n");
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());
68
+ function findCommandsProperty(obj) {
69
+ for (const prop of obj.properties) {
70
+ if (prop.type !== "Property") continue;
71
+ const p = prop;
72
+ if (p.key.type === "Identifier" && p.key.name === "commands" || p.key.type === "Literal" && p.key.value === "commands") return p;
73
+ }
74
+ return null;
63
75
  }
64
- async function setupAndroid(cwd$1) {
65
- await updateGradleCLIPath(await findAppBuildGradle(cwd$1));
76
+ function isRollipopCommandsRequire(prop, content) {
77
+ return content.substring(prop.value.start, prop.value.end).includes("rollipop/commands");
66
78
  }
67
- async function setupIos(cwd$1) {
68
- updateXCodeCLIPath(await findXCodeProject(cwd$1));
79
+ function applyInjection(content, insertOffset, needsComma) {
80
+ const before = content.substring(0, insertOffset);
81
+ const after = content.substring(insertOffset);
82
+ return `${before}${needsComma ? "," : ""}\n commands: ${COMMANDS_REQUIRE},${after}`;
69
83
  }
70
- function setupPackage(cwd$1) {
71
- const packageJsonPath = path.join(cwd$1, "package.json");
84
+ function setupPackage(cwd) {
85
+ const packageJsonPath = path.join(cwd, "package.json");
72
86
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
73
87
  packageJson.devDependencies = {
74
88
  ...packageJson.devDependencies,
75
89
  rollipop: "latest"
76
90
  };
77
- fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
91
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n");
78
92
  }
93
+ //#endregion
94
+ //#region src/index.ts
95
+ const logger = {
96
+ success(message) {
97
+ console.log(pc.green("✓"), message);
98
+ },
99
+ info(message) {
100
+ console.log(pc.cyan("ℹ"), message);
101
+ },
102
+ warn(message) {
103
+ console.log(pc.yellow("⚠"), message);
104
+ },
105
+ error(message, reason) {
106
+ console.error(pc.red("✗"), `${message}: ${reason instanceof Error ? reason.message : String(reason)}`);
107
+ }
108
+ };
109
+ console.log(`\n${pc.bold(pc.cyan("rollipop"))} ${pc.dim("— Modern build toolkit for React Native")}\n`);
79
110
  const cwd = process.cwd();
80
111
  let failed = false;
112
+ let hasWarning = false;
81
113
  try {
82
- await setupAndroid(cwd);
83
- logger.success("Android setup completed");
84
- } catch (error) {
85
- logger.error("Android setup failed", error);
86
- failed ||= true;
87
- }
88
- try {
89
- await setupIos(cwd);
90
- logger.success("iOS setup completed");
114
+ switch (setupReactNativeConfig(cwd)) {
115
+ case "created":
116
+ logger.success(`Created ${pc.bold("react-native.config.js")}`);
117
+ break;
118
+ case "updated":
119
+ logger.success(`Updated ${pc.bold("react-native.config.js")}`);
120
+ break;
121
+ case "already-configured":
122
+ logger.info(`${pc.bold("react-native.config.js")} already configured`);
123
+ break;
124
+ case "manual-required":
125
+ hasWarning = true;
126
+ logger.warn(`Could not auto-configure: ${pc.bold("module.exports")} is not a plain object`);
127
+ console.log(` ${pc.dim("Add the following to your")} ${pc.bold("react-native.config.js")}${pc.dim(":")}\n ${pc.green("commands: require('rollipop/commands'")})`);
128
+ break;
129
+ }
91
130
  } catch (error) {
92
- logger.error("iOS setup failed", error);
131
+ logger.error("React Native CLI setup failed", error);
93
132
  failed ||= true;
94
133
  }
95
- let packageManager = null;
134
+ if (hasWarning || failed) console.log();
96
135
  try {
97
- packageManager = (await whichPm(cwd))?.name ?? null;
98
136
  setupPackage(cwd);
99
- logger.success("Package setup completed");
137
+ logger.success(`Added ${pc.bold("rollipop")} to ${pc.bold("devDependencies")}`);
100
138
  } catch (error) {
101
139
  logger.error("Package setup failed", error);
102
140
  failed ||= true;
103
141
  }
104
142
  if (failed) {
105
- console.warn(`
106
- Failed to setup project automatically
107
-
108
- Please follow the manual setup guide: ${pc.underline("https://rollipop.dev/docs/get-started/quick-start")}`);
143
+ console.log("\n" + pc.red("Setup failed.") + ` Follow the manual guide: ${pc.underline(pc.cyan("https://rollipop.dev/docs/get-started/quick-start"))}`);
109
144
  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
145
  }
118
-
146
+ logger.success(pc.bold("Setup completed!"));
147
+ console.log();
148
+ console.log(` ${pc.dim("Install dependencies manually to finish setup.")}`);
149
+ console.log(` ${pc.dim("See")} ${pc.underline(pc.cyan("https://rollipop.dev"))} ${pc.dim("for configuration details.")}`);
119
150
  //#endregion
120
- export { };
151
+ export {};
package/package.json CHANGED
@@ -1,41 +1,38 @@
1
1
  {
2
2
  "name": "@rollipop/init",
3
- "version": "0.1.0-dev.20260118071417",
4
- "type": "module",
5
- "bin": "./bin/index.js",
6
- "files": [
7
- "bin",
8
- "dist"
9
- ],
3
+ "version": "0.1.0-dev.20260424074146",
4
+ "homepage": "https://github.com/leegeunhyeok/rollipop#readme",
5
+ "bugs": {
6
+ "url": "https://github.com/leegeunhyeok/rollipop/issues"
7
+ },
8
+ "license": "MIT",
9
+ "author": "leegeunhyeok <dev.ghlee@gmail.com> (https://github.com/leegeunhyeok)",
10
10
  "repository": {
11
11
  "type": "git",
12
12
  "url": "git+https://github.com/leegeunhyeok/rollipop.git",
13
13
  "directory": "packages/core"
14
14
  },
15
- "author": "leegeunhyeok <dev.ghlee@gmail.com> (https://github.com/leegeunhyeok)",
16
- "license": "MIT",
17
- "bugs": {
18
- "url": "https://github.com/leegeunhyeok/rollipop/issues"
19
- },
20
- "homepage": "https://github.com/leegeunhyeok/rollipop#readme",
15
+ "bin": "./bin/index.js",
16
+ "files": [
17
+ "bin",
18
+ "dist"
19
+ ],
20
+ "type": "module",
21
21
  "scripts": {
22
- "execute": "tsx src/index.ts",
22
+ "execute": "node --import @oxc-node/core/register src/index.ts",
23
23
  "prepack": "yarn build",
24
24
  "typecheck": "tsc --noEmit",
25
- "test": "vitest --run --passWithNoTests",
26
- "build": "tsdown"
25
+ "test": "vp test --run --passWithNoTests",
26
+ "build": "vp pack"
27
27
  },
28
28
  "dependencies": {
29
- "fast-glob": "^3.3.3",
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
- "tsdown": "0.20.0-beta.1",
36
- "tsx": "^4.21.0",
33
+ "@oxc-node/core": "^0.0.35",
37
34
  "typescript": "5.9.3",
38
- "vitest": "4.0.17"
35
+ "vite-plus": "latest"
39
36
  },
40
- "stableVersion": "0.1.0-alpha.9"
37
+ "stableVersion": "0.1.0-alpha.17"
41
38
  }