Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 91 additions & 82 deletions build/cli.mjs

Large diffs are not rendered by default.

13 changes: 11 additions & 2 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"devDependencies": {
"@bacons/xcode": "^1.0.0-alpha.33",
"@types/bun": "latest",
"@types/node": "^22",
"@types/prompts": "^2.4.9",
"prompts": "^2.4.2"
},
Expand Down
9 changes: 4 additions & 5 deletions scripts/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ async function main() {
const result = await Bun.build({
entrypoints: [join(rootDir, "src", "cli.ts")],
outdir: buildDir,
target: "bun",
target: "node",
minify: true,
naming: "cli.mjs",
// Inline everything - no external deps at runtime
Expand All @@ -41,12 +41,11 @@ async function main() {
process.exit(1);
}

// Ensure shebang is present (bun bundler may or may not preserve it)
// Ensure node shebang for universal compatibility
const outPath = join(buildDir, "cli.mjs");
const bundled = await Bun.file(outPath).text();
if (!bundled.startsWith("#!")) {
await Bun.write(outPath, `#!/usr/bin/env bun\n${bundled}`);
}
const withoutShebang = bundled.replace(/^#!.*\n/, "");
await Bun.write(outPath, `#!/usr/bin/env node\n${withoutShebang}`);

// Make executable
const { chmodSync } = require("fs");
Expand Down
16 changes: 8 additions & 8 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
* Supports both interactive and non-interactive (agent-friendly) modes.
*
* Usage:
* bunx create-xcode # Interactive mode
* bunx create-xcode MyApp # Create iOS app named "MyApp"
* bunx create-xcode MyApp --platform macos # Create macOS app
* bunx create-xcode --list # List available templates
* npx create-xcode # Interactive mode
* npx create-xcode MyApp # Create iOS app named "MyApp"
* npx create-xcode MyApp --platform macos # Create macOS app
* npx create-xcode --list # List available templates
*/

import { existsSync } from "fs";
import { join, resolve } from "path";
// @ts-ignore - parseArgs exists in Node 18.3+ / Bun
import { existsSync } from "node:fs";
import { join, resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { parseArgs } from "node:util";
import prompts from "prompts";
import { TemplateResolver } from "./resolver";
Expand All @@ -26,7 +26,7 @@ let manifest: any;

function findTemplatesDir(): string {
const candidates = [
join(import.meta.dir, "..", "templates"),
join(dirname(fileURLToPath(import.meta.url)), "..", "templates"),
join(process.argv[0] ?? ".", "..", "templates"),
join(process.cwd(), "templates"),
];
Expand Down
27 changes: 12 additions & 15 deletions src/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
* .xcodeproj with source files, build settings, and workspace.
*/

import { existsSync, mkdirSync, statSync, cpSync } from "fs";
import { join, extname, basename } from "path";
import { existsSync, mkdirSync, statSync, cpSync, readFileSync, writeFileSync } from "node:fs";
import { join, extname, basename } from "node:path";
import { execSync } from "node:child_process";
import { build as buildPbxproj } from "@bacons/xcode/json";
import { build as buildWorkspace } from "@bacons/xcode/workspace";
import { build as buildScheme } from "@bacons/xcode/scheme";
Expand Down Expand Up @@ -61,11 +62,7 @@ function isResourceFileType(fileType: string | undefined): boolean {

function getUserName(): string {
try {
const proc = Bun.spawnSync(["id", "-F"]);
return (
new TextDecoder().decode(proc.stdout as unknown as ArrayBuffer).trim() ||
"Unknown"
);
return execSync("id -F", { encoding: "utf-8" }).trim() || "Unknown";
} catch {
return "Unknown";
}
Expand Down Expand Up @@ -120,14 +117,14 @@ export async function generateProject(opts: GenerateOptions): Promise<string> {
vars,
writtenFiles,
);
await Bun.write(join(xcodeprojDir, "project.pbxproj"), pbxproj);
writeFileSync(join(xcodeprojDir, "project.pbxproj"), pbxproj);

// Generate workspace
const workspaceData = buildWorkspace({
version: "1.0",
children: [{ type: "FileRef", location: `self:` }],
} as any);
await Bun.write(
writeFileSync(
join(xcworkspaceDir, "contents.xcworkspacedata"),
workspaceData,
);
Expand All @@ -137,7 +134,7 @@ export async function generateProject(opts: GenerateOptions): Promise<string> {
mkdirSync(schemeDir, { recursive: true });

const schemeXml = buildSchemeXml(name, config, targetUUID);
await Bun.write(join(schemeDir, `${name}.xcscheme`), schemeXml);
writeFileSync(join(schemeDir, `${name}.xcscheme`), schemeXml);

return projectDir;
}
Expand Down Expand Up @@ -183,7 +180,7 @@ async function copyTemplateFiles(
});
} else {
// Read, substitute variables, and write
const content = await Bun.file(sourcePath).text();
const content = readFileSync(sourcePath, "utf-8");
let processed = substituteVariables(content, vars);

// Also substitute ___FILENAME___ with the actual filename
Expand All @@ -197,7 +194,7 @@ async function copyTemplateFiles(
toIdentifier(basename(destName, extname(destName))),
);

await Bun.write(destPath, processed);
writeFileSync(destPath, processed);
writtenFiles.push({
relativePath: destName,
filename: destName,
Expand All @@ -210,23 +207,23 @@ async function copyTemplateFiles(
// Ensure Assets.xcassets exists with AppIcon and AccentColor
const assetsPath = join(sourcesDir, "Assets.xcassets");
mkdirSync(assetsPath, { recursive: true });
await Bun.write(
writeFileSync(
join(assetsPath, "Contents.json"),
JSON.stringify({ info: { author: "xcode", version: 1 } }, null, 2),
);

// AppIcon - always generate (templates don't include it, Xcode generates dynamically)
const appIconDir = join(assetsPath, "AppIcon.appiconset");
mkdirSync(appIconDir, { recursive: true });
await Bun.write(
writeFileSync(
join(appIconDir, "Contents.json"),
JSON.stringify(buildAppIconContents(config.platform), null, 2),
);

// AccentColor
const accentDir = join(assetsPath, "AccentColor.colorset");
mkdirSync(accentDir, { recursive: true });
await Bun.write(
writeFileSync(
join(accentDir, "Contents.json"),
JSON.stringify(
{
Expand Down
Loading