codz-cli 0.0.0-stage → 0.1.2

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 (118) hide show
  1. package/BETA.md +91 -0
  2. package/LICENSE +21 -0
  3. package/README.md +525 -2
  4. package/assets/gradle-wrapper/README.txt +2 -0
  5. package/assets/gradle-wrapper/gradle-wrapper.jar +0 -0
  6. package/assets/gradle-wrapper/gradlew +251 -0
  7. package/assets/gradle-wrapper/gradlew.bat +94 -0
  8. package/bin/codz.js +33 -0
  9. package/dist/adapters/android.js +321 -0
  10. package/dist/adapters/androidSetup.js +277 -0
  11. package/dist/adapters/common.js +114 -0
  12. package/dist/adapters/dartGen.js +83 -0
  13. package/dist/adapters/flutter.js +291 -0
  14. package/dist/adapters/flutterGen.js +204 -0
  15. package/dist/adapters/flutterScan.js +312 -0
  16. package/dist/adapters/flutterSetup.js +252 -0
  17. package/dist/adapters/flutterStyles.js +130 -0
  18. package/dist/adapters/generic.js +20 -0
  19. package/dist/adapters/getx.js +423 -0
  20. package/dist/adapters/index.js +28 -0
  21. package/dist/adapters/ios.js +440 -0
  22. package/dist/adapters/kotlinGen.js +110 -0
  23. package/dist/adapters/models.js +116 -0
  24. package/dist/adapters/nextjs.js +243 -0
  25. package/dist/adapters/nextjsGen.js +285 -0
  26. package/dist/adapters/nextjsScan.js +364 -0
  27. package/dist/adapters/nextjsSetup.js +119 -0
  28. package/dist/adapters/react.js +89 -0
  29. package/dist/adapters/reactNative.js +109 -0
  30. package/dist/adapters/reactNativeSetup.js +236 -0
  31. package/dist/adapters/reactQuery.js +89 -0
  32. package/dist/adapters/reactSetup.js +150 -0
  33. package/dist/adapters/source.js +165 -0
  34. package/dist/cli.js +1175 -0
  35. package/dist/core/cache.js +76 -0
  36. package/dist/core/changeset.js +404 -0
  37. package/dist/core/commands.js +239 -0
  38. package/dist/core/config.js +45 -0
  39. package/dist/core/diff.js +143 -0
  40. package/dist/core/events.js +41 -0
  41. package/dist/core/fsutil.js +122 -0
  42. package/dist/core/git.js +36 -0
  43. package/dist/core/jsonEdit.js +113 -0
  44. package/dist/core/merge.js +159 -0
  45. package/dist/core/proc.js +23 -0
  46. package/dist/core/safe.js +75 -0
  47. package/dist/core/templates.js +100 -0
  48. package/dist/core/tsAst.js +112 -0
  49. package/dist/core/types.js +12 -0
  50. package/dist/core/validateConfig.js +172 -0
  51. package/dist/core/workflow.js +72 -0
  52. package/dist/core/workspace.js +80 -0
  53. package/dist/core/yaml.js +126 -0
  54. package/dist/core/yamlDoc.js +367 -0
  55. package/dist/detector.js +95 -0
  56. package/dist/input/apiSpec.js +395 -0
  57. package/dist/input/curl.js +259 -0
  58. package/dist/input/endpoint.js +220 -0
  59. package/dist/input/har.js +120 -0
  60. package/dist/input/infer.js +174 -0
  61. package/dist/input/naming.js +145 -0
  62. package/dist/input/paste.js +130 -0
  63. package/dist/skills/androidSkeleton.js +292 -0
  64. package/dist/skills/androidTests.js +221 -0
  65. package/dist/skills/androidUi.js +561 -0
  66. package/dist/skills/androidViews.js +536 -0
  67. package/dist/skills/apiAdd.js +99 -0
  68. package/dist/skills/apiDiff.js +271 -0
  69. package/dist/skills/apiEdit.js +547 -0
  70. package/dist/skills/apiImport.js +127 -0
  71. package/dist/skills/apiList.js +35 -0
  72. package/dist/skills/apiSync.js +221 -0
  73. package/dist/skills/auth.js +1304 -0
  74. package/dist/skills/completion.js +142 -0
  75. package/dist/skills/config.js +93 -0
  76. package/dist/skills/create.js +638 -0
  77. package/dist/skills/createDemo.js +1026 -0
  78. package/dist/skills/doctor.js +304 -0
  79. package/dist/skills/doctorFix.js +214 -0
  80. package/dist/skills/explain.js +61 -0
  81. package/dist/skills/feature.js +418 -0
  82. package/dist/skills/featureEdit.js +225 -0
  83. package/dist/skills/featureLinks.js +179 -0
  84. package/dist/skills/init.js +66 -0
  85. package/dist/skills/iosUi.js +468 -0
  86. package/dist/skills/locale.js +335 -0
  87. package/dist/skills/mcp.js +175 -0
  88. package/dist/skills/mock.js +482 -0
  89. package/dist/skills/module.js +23 -0
  90. package/dist/skills/plans.js +66 -0
  91. package/dist/skills/prefill.js +84 -0
  92. package/dist/skills/reactNativeUi.js +388 -0
  93. package/dist/skills/reactUi.js +352 -0
  94. package/dist/skills/report.js +75 -0
  95. package/dist/skills/route.js +230 -0
  96. package/dist/skills/screen.js +597 -0
  97. package/dist/skills/screenForm.js +410 -0
  98. package/dist/skills/setup.js +28 -0
  99. package/dist/skills/setupDoctor.js +226 -0
  100. package/dist/skills/templatesCmd.js +32 -0
  101. package/dist/skills/testgen.js +331 -0
  102. package/dist/skills/theme.js +388 -0
  103. package/dist/starters/auth.js +144 -0
  104. package/dist/starters/commands.js +283 -0
  105. package/dist/starters/engine.js +315 -0
  106. package/dist/starters/home.js +42 -0
  107. package/dist/starters/http.js +93 -0
  108. package/dist/starters/manifest.js +285 -0
  109. package/dist/starters/paths.js +70 -0
  110. package/dist/starters/publish.js +150 -0
  111. package/dist/starters/registry.js +258 -0
  112. package/dist/starters/sign.js +72 -0
  113. package/dist/starters/source.js +98 -0
  114. package/dist/starters/strictJson.js +148 -0
  115. package/dist/starters/tar.js +180 -0
  116. package/docs/COMMANDS.md +527 -0
  117. package/examples/openapi.yaml +319 -0
  118. package/package.json +57 -4
@@ -0,0 +1,638 @@
1
+ // codz create: a new project, set up the way codz would set it up.
2
+ //
3
+ // 1. project type (Flutter, Next.js, ...) and its architecture
4
+ // 2. name and type-specific options (organization, Tailwind, ...)
5
+ // 3. the platform's own creator (flutter create, create-next-app) when it is
6
+ // installed and reachable, otherwise a minimal project codz writes itself
7
+ // 4. the architecture applied with `setup`, codz.config.yaml written,
8
+ // an optional theme color, dependencies installed, a first git commit
9
+ //
10
+ // Each type is one entry in PROJECT_TYPES, so more platforms plug in the same way.
11
+ import { checkBaseUrl } from "../core/safe.js";
12
+ import { existsSync, mkdirSync, readdirSync, writeFileSync } from 'node:fs';
13
+ import { dirname, join, resolve } from 'node:path';
14
+ import { addGradleWrapper, androidPackage, androidSkeleton } from "./androidSkeleton.js";
15
+ import { ADAPTERS } from "../adapters/index.js";
16
+ import { applyChangeSet, buildChangeSet } from "../core/changeset.js";
17
+ import { ToolError } from "../core/types.js";
18
+ import { DART_RESERVED } from "../input/naming.js";
19
+ import { detectProject, initFiles, writeInitFiles } from "./init.js";
20
+ import { planSetup } from "./setup.js";
21
+ import { planDemo } from "./createDemo.js";
22
+ import { planThemeSet } from "./theme.js";
23
+ import { runTool, toolMissing } from "../core/proc.js";
24
+ const NONE = { id: 'none', label: 'None', description: 'No API layer yet; set one up later with codz setup' };
25
+ function flutterSkeleton(o) {
26
+ const files = new Map();
27
+ files.set('pubspec.yaml', `name: ${o.name}\ndescription: "A new Flutter project."\npublish_to: 'none'\n\nversion: 1.0.0+1\n\nenvironment:\n sdk: ^3.5.0\n\ndependencies:\n flutter:\n sdk: flutter\n\n cupertino_icons: ^1.0.8\n\ndev_dependencies:\n flutter_test:\n sdk: flutter\n\n flutter_lints: ^4.0.0\n\nflutter:\n uses-material-design: true\n`);
28
+ files.set('analysis_options.yaml', 'include: package:flutter_lints/flutter.yaml\n');
29
+ files.set('.gitignore', '.dart_tool/\n.flutter-plugins\n.flutter-plugins-dependencies\nbuild/\n.idea/\n*.iml\n');
30
+ const title = o.name.split('_').map((w) => w[0].toUpperCase() + w.slice(1)).join(' ');
31
+ files.set('lib/main.dart', [
32
+ "import 'package:flutter/material.dart';",
33
+ '',
34
+ 'void main() {',
35
+ ' runApp(const MyApp());',
36
+ '}',
37
+ '',
38
+ 'class MyApp extends StatelessWidget {',
39
+ ' const MyApp({super.key});',
40
+ '',
41
+ ' @override',
42
+ ' Widget build(BuildContext context) {',
43
+ ' return MaterialApp(',
44
+ ` title: '${title}',`,
45
+ ' theme: ThemeData(',
46
+ ' colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),',
47
+ ' useMaterial3: true,',
48
+ ' ),',
49
+ ` home: const HomePage(title: '${title}'),`,
50
+ ' );',
51
+ ' }',
52
+ '}',
53
+ '',
54
+ 'class HomePage extends StatelessWidget {',
55
+ ' const HomePage({super.key, required this.title});',
56
+ '',
57
+ ' final String title;',
58
+ '',
59
+ ' @override',
60
+ ' Widget build(BuildContext context) {',
61
+ ' return Scaffold(',
62
+ ' appBar: AppBar(title: Text(title)),',
63
+ " body: const Center(child: Text('Hello')),",
64
+ ' );',
65
+ ' }',
66
+ '}',
67
+ '',
68
+ ].join('\n'));
69
+ files.set('test/widget_test.dart', `import 'package:flutter_test/flutter_test.dart';\n\nimport 'package:${o.name}/main.dart';\n\nvoid main() {\n testWidgets('shows the home page', (WidgetTester tester) async {\n await tester.pumpWidget(const MyApp());\n expect(find.text('Hello'), findsOneWidget);\n });\n}\n`);
70
+ return files;
71
+ }
72
+ function nextSkeleton(o) {
73
+ const files = new Map();
74
+ const pkg = {
75
+ name: o.name,
76
+ version: '0.1.0',
77
+ private: true,
78
+ scripts: { dev: 'next dev', build: 'next build', start: 'next start', lint: 'next lint' },
79
+ dependencies: { next: '^15.0.0', react: '^19.0.0', 'react-dom': '^19.0.0' },
80
+ devDependencies: { '@types/node': '^20', '@types/react': '^19', '@types/react-dom': '^19', typescript: '^5', ...(o.tailwind ? { tailwindcss: '^4', '@tailwindcss/postcss': '^4' } : {}) },
81
+ };
82
+ files.set('package.json', JSON.stringify(pkg, null, 2) + '\n');
83
+ files.set('tsconfig.json', JSON.stringify({
84
+ compilerOptions: { target: 'ES2017', lib: ['dom', 'dom.iterable', 'esnext'], allowJs: true, skipLibCheck: true, strict: true, noEmit: true, esModuleInterop: true, module: 'esnext', moduleResolution: 'bundler', resolveJsonModule: true, isolatedModules: true, jsx: 'preserve', incremental: true, plugins: [{ name: 'next' }], paths: { '@/*': ['./*'] } },
85
+ include: ['next-env.d.ts', '**/*.ts', '**/*.tsx', '.next/types/**/*.ts'],
86
+ exclude: ['node_modules'],
87
+ }, null, 2) + '\n');
88
+ files.set('next.config.mjs', '/** @type {import(\'next\').NextConfig} */\nconst nextConfig = {};\n\nexport default nextConfig;\n');
89
+ files.set('next-env.d.ts', '/// <reference types="next" />\n/// <reference types="next/image-types/global" />\n');
90
+ files.set('.gitignore', '/node_modules\n/.next/\n/out/\n.env*.local\nnext-env.d.ts\n*.tsbuildinfo\n');
91
+ if (o.tailwind) {
92
+ files.set('postcss.config.mjs', 'const config = {\n plugins: { "@tailwindcss/postcss": {} },\n};\n\nexport default config;\n');
93
+ files.set('app/globals.css', '@import "tailwindcss";\n');
94
+ }
95
+ else
96
+ files.set('app/globals.css', ':root {\n font-family: system-ui, sans-serif;\n}\n');
97
+ files.set('app/layout.tsx', `import type { Metadata } from 'next';\nimport './globals.css';\n\nexport const metadata: Metadata = {\n title: '${o.name}',\n};\n\nexport default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {\n return (\n <html lang="en">\n <body>{children}</body>\n </html>\n );\n}\n`);
98
+ files.set('app/page.tsx', `export default function Home() {\n return (\n <main>\n <h1>${o.name}</h1>\n </main>\n );\n}\n`);
99
+ return files;
100
+ }
101
+ /** React Native: Expo (blank TypeScript template) or the React Native CLI template, without native folders. */
102
+ function reactNativeSkeleton(o) {
103
+ const files = new Map();
104
+ const expo = o.flavor !== 'bare';
105
+ const display = o.name.replace(/[-_.]+(\w)/g, (_m, c) => c.toUpperCase()).replace(/^\w/, (c) => c.toUpperCase());
106
+ const pkg = expo
107
+ ? {
108
+ name: o.name,
109
+ version: '1.0.0',
110
+ main: 'index.ts',
111
+ scripts: { start: 'expo start', android: 'expo start --android', ios: 'expo start --ios', test: 'jest' },
112
+ dependencies: { expo: '~52.0.0', 'expo-status-bar': '~2.0.0', react: '18.3.1', 'react-native': '0.76.5' },
113
+ devDependencies: { '@babel/core': '^7.25.0', '@types/react': '~18.3.12', typescript: '^5.3.3', jest: '^29.7.0', 'jest-expo': '~52.0.0' },
114
+ jest: { preset: 'jest-expo' },
115
+ private: true,
116
+ }
117
+ : {
118
+ name: o.name,
119
+ version: '0.0.1',
120
+ private: true,
121
+ scripts: { android: 'react-native run-android', ios: 'react-native run-ios', start: 'react-native start', test: 'jest', lint: 'eslint .' },
122
+ dependencies: { react: '18.3.1', 'react-native': '0.76.5' },
123
+ devDependencies: { '@babel/core': '^7.25.2', '@react-native/babel-preset': '0.76.5', '@react-native/metro-config': '0.76.5', '@react-native/typescript-config': '0.76.5', '@types/react': '^18.2.6', jest: '^29.6.3', typescript: '5.0.4' },
124
+ jest: { preset: 'react-native' },
125
+ engines: { node: '>=18' },
126
+ };
127
+ files.set('package.json', JSON.stringify(pkg, null, 2) + '\n');
128
+ files.set('tsconfig.json', JSON.stringify(expo ? { extends: 'expo/tsconfig.base', compilerOptions: { strict: true, paths: { '@/*': ['./src/*'] } } } : { extends: '@react-native/typescript-config/tsconfig.json' }, null, 2) + '\n');
129
+ files.set('app.json', JSON.stringify(expo ? { expo: { name: display, slug: o.name, version: '1.0.0', orientation: 'portrait', ios: { bundleIdentifier: `${o.org}.${o.name.replace(/[^a-zA-Z0-9]/g, '')}` }, android: { package: `${o.org}.${o.name.replace(/[^a-zA-Z0-9]/g, '').toLowerCase()}` } } } : { name: display, displayName: display }, null, 2) + '\n');
130
+ files.set('App.tsx', `import { StyleSheet, Text, View } from 'react-native';\n\nexport default function App() {\n return (\n <View style={styles.container}>\n <Text>${display}</Text>\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n container: { flex: 1, alignItems: 'center', justifyContent: 'center' },\n});\n`);
131
+ if (expo) {
132
+ files.set('index.ts', "import { registerRootComponent } from 'expo';\n\nimport App from './App';\n\nregisterRootComponent(App);\n");
133
+ files.set('.gitignore', 'node_modules/\n.expo/\ndist/\nweb-build/\n.env*.local\n*.jks\n*.p8\n*.p12\n*.key\n*.mobileprovision\n');
134
+ }
135
+ else {
136
+ files.set('index.js', `import { AppRegistry } from 'react-native';\nimport App from './App';\nimport { name as appName } from './app.json';\n\nAppRegistry.registerComponent(appName, () => App);\n`);
137
+ files.set('babel.config.js', "module.exports = {\n presets: ['module:@react-native/babel-preset'],\n};\n");
138
+ files.set('metro.config.js', "const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');\n\nmodule.exports = mergeConfig(getDefaultConfig(__dirname), {});\n");
139
+ files.set('.gitignore', 'node_modules/\n.DS_Store\nbuild/\n*.log\nios/Pods/\n.gradle/\nlocal.properties\n*.keystore\n!debug.keystore\n');
140
+ }
141
+ return files;
142
+ }
143
+ /** React web app with Vite and TypeScript (what npm create vite gives you, react-ts template). */
144
+ function reactSkeleton(o) {
145
+ const files = new Map();
146
+ const title = o.name.split(/[-_.]/).filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join(' ');
147
+ files.set('package.json', JSON.stringify({
148
+ name: o.name,
149
+ private: true,
150
+ version: '0.0.0',
151
+ type: 'module',
152
+ scripts: { dev: 'vite', build: 'tsc -b && vite build', preview: 'vite preview', test: 'vitest run' },
153
+ dependencies: { react: '^18.3.1', 'react-dom': '^18.3.1' },
154
+ devDependencies: { '@types/react': '^18.3.12', '@types/react-dom': '^18.3.1', '@vitejs/plugin-react': '^4.3.4', typescript: '~5.6.2', vite: '^6.0.1', vitest: '^2.1.8' },
155
+ }, null, 2) + '\n');
156
+ files.set('index.html', `<!doctype html>\n<html lang="en">\n <head>\n <meta charset="UTF-8" />\n <meta name="viewport" content="width=device-width, initial-scale=1.0" />\n <title>${title}</title>\n </head>\n <body>\n <div id="root"></div>\n <script type="module" src="/src/main.tsx"></script>\n </body>\n</html>\n`);
157
+ files.set('vite.config.ts', `import { defineConfig } from 'vite'\nimport react from '@vitejs/plugin-react'\n\nexport default defineConfig({\n plugins: [react()],\n})\n`);
158
+ files.set('tsconfig.json', JSON.stringify({ compilerOptions: { target: 'ES2020', lib: ['ES2020', 'DOM', 'DOM.Iterable'], module: 'ESNext', moduleResolution: 'bundler', jsx: 'react-jsx', strict: true, noEmit: true, allowImportingTsExtensions: true, skipLibCheck: true, isolatedModules: true }, include: ['src'] }, null, 2) + '\n');
159
+ files.set('src/vite-env.d.ts', '/// <reference types="vite/client" />\n');
160
+ files.set('src/main.tsx', `import { StrictMode } from 'react'\nimport { createRoot } from 'react-dom/client'\nimport App from './App.tsx'\n\ncreateRoot(document.getElementById('root')!).render(\n <StrictMode>\n <App />\n </StrictMode>,\n)\n`);
161
+ files.set('src/App.tsx', `function App() {\n return (\n <main>\n <h1>${title}</h1>\n </main>\n )\n}\n\nexport default App\n`);
162
+ files.set('.gitignore', 'node_modules\ndist\n*.local\n.codz/\n');
163
+ return files;
164
+ }
165
+ /** The Swift name of an iOS app: shop-app → ShopApp. */
166
+ export const iosAppName = (name) => name.split(/[-_]/).filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join('');
167
+ /**
168
+ * A SwiftUI app as Xcode 16's App template makes it: one target whose sources
169
+ * are a synchronized folder (new files are part of the app without editing the
170
+ * project file), an asset catalog, and a generated Info.plist.
171
+ */
172
+ function iosSkeleton(o) {
173
+ const app = iosAppName(o.name);
174
+ const bundle = `${o.org}.${o.name.replace(/[^a-zA-Z0-9]/g, '')}`;
175
+ const files = new Map();
176
+ // Object ids: 24 hex digits, fixed so a project made twice is the same.
177
+ const id = (n) => `C0DE${String(n).padStart(20, '0')}`;
178
+ const [project, mainGroup, productsGroup, appGroup, target, product, sources, frameworks, resources, projectList, targetList, projDebug, projRelease, targetDebug, targetRelease] = Array.from({ length: 15 }, (_, i) => id(i + 1));
179
+ const common = [
180
+ 'ALWAYS_SEARCH_USER_PATHS = NO;',
181
+ 'ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;',
182
+ 'CLANG_ENABLE_MODULES = YES;',
183
+ 'CLANG_ENABLE_OBJC_ARC = YES;',
184
+ 'COPY_PHASE_STRIP = NO;',
185
+ 'ENABLE_STRICT_OBJC_MSGSEND = YES;',
186
+ 'ENABLE_USER_SCRIPT_SANDBOXING = YES;',
187
+ 'GCC_C_LANGUAGE_STANDARD = gnu17;',
188
+ 'IPHONEOS_DEPLOYMENT_TARGET = 17.0;',
189
+ 'LOCALIZATION_PREFERS_STRING_CATALOGS = YES;',
190
+ 'SDKROOT = iphoneos;',
191
+ ];
192
+ const projSettings = (debug) => [
193
+ ...common,
194
+ ...(debug
195
+ ? ['DEBUG_INFORMATION_FORMAT = dwarf;', 'ENABLE_TESTABILITY = YES;', 'GCC_OPTIMIZATION_LEVEL = 0;', 'ONLY_ACTIVE_ARCH = YES;', 'SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";', 'SWIFT_OPTIMIZATION_LEVEL = "-Onone";', 'MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;']
196
+ : ['DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";', 'ENABLE_NS_ASSERTIONS = NO;', 'SWIFT_COMPILATION_MODE = wholemodule;', 'VALIDATE_PRODUCT = YES;', 'MTL_ENABLE_DEBUG_INFO = NO;']),
197
+ ];
198
+ const targetSettings = [
199
+ 'ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;',
200
+ 'ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;',
201
+ 'CODE_SIGN_STYLE = Automatic;',
202
+ 'CURRENT_PROJECT_VERSION = 1;',
203
+ 'ENABLE_PREVIEWS = YES;',
204
+ 'GENERATE_INFOPLIST_FILE = YES;',
205
+ `INFOPLIST_KEY_CFBundleDisplayName = ${app};`,
206
+ 'INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;',
207
+ 'INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;',
208
+ 'INFOPLIST_KEY_UILaunchScreen_Generation = YES;',
209
+ 'INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";',
210
+ 'INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";',
211
+ 'LD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t"$(inherited)",\n\t\t\t\t\t"@executable_path/Frameworks",\n\t\t\t\t);',
212
+ 'MARKETING_VERSION = 1.0;',
213
+ `PRODUCT_BUNDLE_IDENTIFIER = ${bundle};`,
214
+ 'PRODUCT_NAME = "$(TARGET_NAME)";',
215
+ 'SWIFT_EMIT_LOC_STRINGS = YES;',
216
+ 'SWIFT_VERSION = 5.0;',
217
+ 'TARGETED_DEVICE_FAMILY = "1,2";',
218
+ ];
219
+ const config = (key, name, settings) => [`\t\t${key} /* ${name} */ = {`, '\t\t\tisa = XCBuildConfiguration;', '\t\t\tbuildSettings = {', ...settings.map((x) => `\t\t\t\t${x}`), '\t\t\t};', `\t\t\tname = ${name};`, '\t\t};'];
220
+ const phase = (key, isa, name) => [`\t\t${key} /* ${name} */ = {`, `\t\t\tisa = ${isa};`, '\t\t\tbuildActionMask = 2147483647;', '\t\t\tfiles = (', '\t\t\t);', '\t\t\trunOnlyForDeploymentPostprocessing = 0;', '\t\t};'];
221
+ const pbx = [
222
+ '// !$*UTF8*$!',
223
+ '{',
224
+ '\tarchiveVersion = 1;',
225
+ '\tclasses = {',
226
+ '\t};',
227
+ '\tobjectVersion = 77;',
228
+ '\tobjects = {',
229
+ '',
230
+ '/* Begin PBXFileReference section */',
231
+ `\t\t${product} /* ${app}.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ${app}.app; sourceTree = BUILT_PRODUCTS_DIR; };`,
232
+ '/* End PBXFileReference section */',
233
+ '',
234
+ '/* Begin PBXFileSystemSynchronizedRootGroup section */',
235
+ `\t\t${appGroup} /* ${app} */ = {`,
236
+ '\t\t\tisa = PBXFileSystemSynchronizedRootGroup;',
237
+ `\t\t\tpath = ${app};`,
238
+ '\t\t\tsourceTree = "<group>";',
239
+ '\t\t};',
240
+ '/* End PBXFileSystemSynchronizedRootGroup section */',
241
+ '',
242
+ '/* Begin PBXFrameworksBuildPhase section */',
243
+ ...phase(frameworks, 'PBXFrameworksBuildPhase', 'Frameworks'),
244
+ '/* End PBXFrameworksBuildPhase section */',
245
+ '',
246
+ '/* Begin PBXGroup section */',
247
+ `\t\t${mainGroup} = {`,
248
+ '\t\t\tisa = PBXGroup;',
249
+ '\t\t\tchildren = (',
250
+ `\t\t\t\t${appGroup} /* ${app} */,`,
251
+ `\t\t\t\t${productsGroup} /* Products */,`,
252
+ '\t\t\t);',
253
+ '\t\t\tsourceTree = "<group>";',
254
+ '\t\t};',
255
+ `\t\t${productsGroup} /* Products */ = {`,
256
+ '\t\t\tisa = PBXGroup;',
257
+ '\t\t\tchildren = (',
258
+ `\t\t\t\t${product} /* ${app}.app */,`,
259
+ '\t\t\t);',
260
+ '\t\t\tname = Products;',
261
+ '\t\t\tsourceTree = "<group>";',
262
+ '\t\t};',
263
+ '/* End PBXGroup section */',
264
+ '',
265
+ '/* Begin PBXNativeTarget section */',
266
+ `\t\t${target} /* ${app} */ = {`,
267
+ '\t\t\tisa = PBXNativeTarget;',
268
+ `\t\t\tbuildConfigurationList = ${targetList} /* Build configuration list for PBXNativeTarget "${app}" */;`,
269
+ '\t\t\tbuildPhases = (',
270
+ `\t\t\t\t${sources} /* Sources */,`,
271
+ `\t\t\t\t${frameworks} /* Frameworks */,`,
272
+ `\t\t\t\t${resources} /* Resources */,`,
273
+ '\t\t\t);',
274
+ '\t\t\tbuildRules = (',
275
+ '\t\t\t);',
276
+ '\t\t\tdependencies = (',
277
+ '\t\t\t);',
278
+ '\t\t\tfileSystemSynchronizedGroups = (',
279
+ `\t\t\t\t${appGroup} /* ${app} */,`,
280
+ '\t\t\t);',
281
+ `\t\t\tname = ${app};`,
282
+ '\t\t\tpackageProductDependencies = (',
283
+ '\t\t\t);',
284
+ `\t\t\tproductName = ${app};`,
285
+ `\t\t\tproductReference = ${product} /* ${app}.app */;`,
286
+ '\t\t\tproductType = "com.apple.product-type.application";',
287
+ '\t\t};',
288
+ '/* End PBXNativeTarget section */',
289
+ '',
290
+ '/* Begin PBXProject section */',
291
+ `\t\t${project} /* Project object */ = {`,
292
+ '\t\t\tisa = PBXProject;',
293
+ '\t\t\tattributes = {',
294
+ '\t\t\t\tBuildIndependentTargetsInParallel = 1;',
295
+ '\t\t\t\tLastSwiftUpdateCheck = 1600;',
296
+ '\t\t\t\tLastUpgradeCheck = 1600;',
297
+ '\t\t\t\tTargetAttributes = {',
298
+ `\t\t\t\t\t${target} = {`,
299
+ '\t\t\t\t\t\tCreatedOnToolsVersion = 16.0;',
300
+ '\t\t\t\t\t};',
301
+ '\t\t\t\t};',
302
+ '\t\t\t};',
303
+ `\t\t\tbuildConfigurationList = ${projectList} /* Build configuration list for PBXProject "${app}" */;`,
304
+ '\t\t\tdevelopmentRegion = en;',
305
+ '\t\t\thasScannedForEncodings = 0;',
306
+ '\t\t\tknownRegions = (',
307
+ '\t\t\t\ten,',
308
+ '\t\t\t\tBase,',
309
+ '\t\t\t);',
310
+ `\t\t\tmainGroup = ${mainGroup};`,
311
+ '\t\t\tminimizedProjectReferenceProxies = 1;',
312
+ '\t\t\tpreferredProjectObjectVersion = 77;',
313
+ `\t\t\tproductRefGroup = ${productsGroup} /* Products */;`,
314
+ '\t\t\tprojectDirPath = "";',
315
+ '\t\t\tprojectRoot = "";',
316
+ '\t\t\ttargets = (',
317
+ `\t\t\t\t${target} /* ${app} */,`,
318
+ '\t\t\t);',
319
+ '\t\t};',
320
+ '/* End PBXProject section */',
321
+ '',
322
+ '/* Begin PBXResourcesBuildPhase section */',
323
+ ...phase(resources, 'PBXResourcesBuildPhase', 'Resources'),
324
+ '/* End PBXResourcesBuildPhase section */',
325
+ '',
326
+ '/* Begin PBXSourcesBuildPhase section */',
327
+ ...phase(sources, 'PBXSourcesBuildPhase', 'Sources'),
328
+ '/* End PBXSourcesBuildPhase section */',
329
+ '',
330
+ '/* Begin XCBuildConfiguration section */',
331
+ ...config(projDebug, 'Debug', projSettings(true)),
332
+ ...config(projRelease, 'Release', projSettings(false)),
333
+ ...config(targetDebug, 'Debug', targetSettings),
334
+ ...config(targetRelease, 'Release', targetSettings),
335
+ '/* End XCBuildConfiguration section */',
336
+ '',
337
+ '/* Begin XCConfigurationList section */',
338
+ `\t\t${projectList} /* Build configuration list for PBXProject "${app}" */ = {`,
339
+ '\t\t\tisa = XCConfigurationList;',
340
+ '\t\t\tbuildConfigurations = (',
341
+ `\t\t\t\t${projDebug} /* Debug */,`,
342
+ `\t\t\t\t${projRelease} /* Release */,`,
343
+ '\t\t\t);',
344
+ '\t\t\tdefaultConfigurationIsVisible = 0;',
345
+ '\t\t\tdefaultConfigurationName = Release;',
346
+ '\t\t};',
347
+ `\t\t${targetList} /* Build configuration list for PBXNativeTarget "${app}" */ = {`,
348
+ '\t\t\tisa = XCConfigurationList;',
349
+ '\t\t\tbuildConfigurations = (',
350
+ `\t\t\t\t${targetDebug} /* Debug */,`,
351
+ `\t\t\t\t${targetRelease} /* Release */,`,
352
+ '\t\t\t);',
353
+ '\t\t\tdefaultConfigurationIsVisible = 0;',
354
+ '\t\t\tdefaultConfigurationName = Release;',
355
+ '\t\t};',
356
+ '/* End XCConfigurationList section */',
357
+ '\t};',
358
+ `\trootObject = ${project} /* Project object */;`,
359
+ '}',
360
+ '',
361
+ ].join('\n');
362
+ files.set(`${app}.xcodeproj/project.pbxproj`, pbx);
363
+ files.set(`${app}/${app}App.swift`, `import SwiftUI\n\n@main\nstruct ${app}App: App {\n var body: some Scene {\n WindowGroup {\n ContentView()\n }\n }\n}\n`);
364
+ files.set(`${app}/ContentView.swift`, `import SwiftUI\n\nstruct ContentView: View {\n var body: some View {\n Text("${app}")\n .padding()\n }\n}\n\n#Preview {\n ContentView()\n}\n`);
365
+ files.set(`${app}/Assets.xcassets/Contents.json`, '{\n "info" : {\n "author" : "xcode",\n "version" : 1\n }\n}\n');
366
+ files.set(`${app}/Assets.xcassets/AccentColor.colorset/Contents.json`, '{\n "colors" : [\n {\n "idiom" : "universal"\n }\n ],\n "info" : {\n "author" : "xcode",\n "version" : 1\n }\n}\n');
367
+ files.set(`${app}/Assets.xcassets/AppIcon.appiconset/Contents.json`, '{\n "images" : [\n {\n "idiom" : "universal",\n "platform" : "ios",\n "size" : "1024x1024"\n }\n ],\n "info" : {\n "author" : "xcode",\n "version" : 1\n }\n}\n');
368
+ files.set('.gitignore', 'xcuserdata/\nDerivedData/\n.DS_Store\n.codz/\n');
369
+ return files;
370
+ }
371
+ const npmName = (name) => (/^[a-z0-9][a-z0-9._-]*$/.test(name) && name.length <= 214 ? null : 'Use lowercase letters, digits, dots, dashes and underscores, starting with a letter or digit (for example my-app).');
372
+ export const PROJECT_TYPES = [
373
+ {
374
+ id: 'flutter',
375
+ label: 'Flutter',
376
+ description: 'Android, iOS, web and desktop from one Dart codebase',
377
+ architectures: [
378
+ { id: 'riverpod', label: 'Riverpod', description: 'Dio through dioProvider; a provider per repository' },
379
+ { id: 'getx', label: 'GetX', description: 'GetX modules (binding, controller, view), Dio registered in an InitialBinding' },
380
+ { id: 'bloc', label: 'Bloc', description: 'Cubits for screens, get_it for Dio and repositories' },
381
+ { id: 'get_it', label: 'get_it', description: 'Service locator for Dio and repositories, no state library' },
382
+ { ...NONE, label: 'Plain Flutter', description: 'No state library or API layer; add one later with codz setup' },
383
+ ],
384
+ checkName: (n) => (!/^[a-z][a-z0-9_]*$/.test(n) ? 'Use lowercase letters, digits and underscores, starting with a letter (for example my_app).' : DART_RESERVED.has(n) ? `"${n}" is a Dart keyword.` : null),
385
+ needsOrg: true,
386
+ options: [],
387
+ creator: (o) => ({ cmd: 'flutter', args: ['create', '--org', o.org, '--project-name', o.name, o.name], label: 'flutter create' }),
388
+ skeleton: flutterSkeleton,
389
+ install: () => ({ cmd: 'flutter', args: ['pub', 'get'], label: 'flutter pub get' }),
390
+ framework: 'flutter',
391
+ },
392
+ {
393
+ id: 'nextjs',
394
+ label: 'Next.js',
395
+ description: 'React web app with the App Router and TypeScript',
396
+ architectures: [
397
+ { id: 'fetch-helper', label: 'Fetch helper', description: 'lib/api/client.ts with apiFetch<T>() and ApiError; API functions call it' },
398
+ { id: 'plain-fetch', label: 'Plain fetch', description: 'Each API function calls fetch with the base URL from the environment' },
399
+ NONE,
400
+ ],
401
+ checkName: npmName,
402
+ needsOrg: false,
403
+ options: ['tailwind'],
404
+ creator: (o) => ({
405
+ cmd: process.platform === 'win32' ? 'npx.cmd' : 'npx',
406
+ args: ['--yes', 'create-next-app@latest', o.name, '--ts', '--app', '--eslint', o.tailwind ? '--tailwind' : '--no-tailwind', '--no-src-dir', '--import-alias', '@/*', '--use-npm', '--skip-install', '--yes'],
407
+ label: 'create-next-app',
408
+ }),
409
+ skeleton: nextSkeleton,
410
+ install: () => ({ cmd: process.platform === 'win32' ? 'npm.cmd' : 'npm', args: ['install'], label: 'npm install' }),
411
+ framework: 'nextjs',
412
+ },
413
+ {
414
+ id: 'react-native',
415
+ label: 'React Native',
416
+ description: 'Android and iOS apps in TypeScript, with Expo or the React Native CLI',
417
+ architectures: [
418
+ { id: 'fetch-helper', label: 'Fetch helper', description: 'apiFetch<T>() in src/api, API URL in src/config/env.ts, React Navigation' },
419
+ { id: 'axios-react-query', label: 'axios + TanStack Query', description: 'An axios instance, a query hook per endpoint, React Navigation' },
420
+ { id: 'axios', label: 'axios', description: 'An axios instance in src/api, React Navigation' },
421
+ NONE,
422
+ ],
423
+ checkName: (n) => (/^[a-z][a-z0-9-]*$/.test(n) ? null : 'Use lowercase letters, digits and dashes, starting with a letter (for example shop-app).'),
424
+ needsOrg: true,
425
+ options: ['flavor'],
426
+ creator: (o) => o.flavor === 'bare'
427
+ ? { cmd: process.platform === 'win32' ? 'npx.cmd' : 'npx', args: ['--yes', '@react-native-community/cli@latest', 'init', o.name.replace(/-(\w)/g, (_m, c) => c.toUpperCase()).replace(/^\w/, (c) => c.toUpperCase()), '--directory', o.name, '--package-name', `${o.org}.${o.name.replace(/-/g, '')}`, '--skip-install', '--skip-git-init'], label: 'React Native CLI (init)' }
428
+ : { cmd: process.platform === 'win32' ? 'npx.cmd' : 'npx', args: ['--yes', 'create-expo-app@latest', o.name, '--template', 'blank-typescript', '--no-install'], label: 'create-expo-app' },
429
+ skeleton: reactNativeSkeleton,
430
+ install: () => ({ cmd: process.platform === 'win32' ? 'npm.cmd' : 'npm', args: ['install'], label: 'npm install' }),
431
+ framework: 'react-native',
432
+ },
433
+ {
434
+ id: 'android',
435
+ label: 'Android (Kotlin)',
436
+ description: 'Native Android app in Kotlin with Jetpack Compose and Navigation Compose',
437
+ architectures: [
438
+ { id: 'retrofit-hilt', label: 'Retrofit + Hilt', description: 'Retrofit, kotlinx.serialization and OkHttp provided by a Hilt NetworkModule' },
439
+ { id: 'retrofit-koin', label: 'Retrofit + Koin', description: 'Retrofit and API interfaces in a Koin module, started in the Application class' },
440
+ { id: 'retrofit', label: 'Retrofit only', description: 'A ServiceLocator object holds Retrofit and the API interfaces; no DI library' },
441
+ { ...NONE, label: 'Plain Compose', description: 'Compose and Navigation only; add an API layer later with codz setup' },
442
+ ],
443
+ checkName: (n) => (/^[a-z][a-z0-9-]*$/.test(n) ? null : 'Use lowercase letters, digits and dashes, starting with a letter (for example shop-app).'),
444
+ needsOrg: true,
445
+ options: [],
446
+ // Android has no command-line project creator; codz writes the Empty Activity template itself.
447
+ creator: () => null,
448
+ skeleton: (o) => androidSkeleton(o),
449
+ install: () => null,
450
+ framework: 'android',
451
+ },
452
+ {
453
+ id: 'react',
454
+ label: 'React (Vite)',
455
+ description: 'React web app with Vite, TypeScript and React Router',
456
+ architectures: [
457
+ { id: 'fetch-helper', label: 'Fetch helper', description: 'src/api/client.ts with apiFetch<T>() and ApiError; API URL in src/config/env.ts' },
458
+ { id: 'axios', label: 'axios', description: 'An axios instance in src/api/client.ts; API functions call it' },
459
+ { id: 'axios-react-query', label: 'axios + TanStack Query', description: 'The axios instance, a query hook per endpoint, QueryClientProvider around the app' },
460
+ { ...NONE, label: 'Plain React', description: 'No API layer or router yet; add one later with codz setup' },
461
+ ],
462
+ checkName: npmName,
463
+ needsOrg: false,
464
+ options: [],
465
+ // codz writes the react-ts template itself (npm create vite asks questions in some versions).
466
+ creator: () => null,
467
+ skeleton: reactSkeleton,
468
+ install: () => ({ cmd: process.platform === 'win32' ? 'npm.cmd' : 'npm', args: ['install'], label: 'npm install' }),
469
+ framework: 'react',
470
+ },
471
+ {
472
+ id: 'ios',
473
+ label: 'iOS (SwiftUI)',
474
+ description: 'Native iPhone and iPad app in Swift with SwiftUI (open it in Xcode 16 or newer)',
475
+ architectures: [
476
+ { id: 'urlsession', label: 'URLSession + async/await', description: 'APIClient with send<T>() over URLSession and JSONDecoder; ObservableObject view models' },
477
+ { ...NONE, label: 'Plain SwiftUI', description: 'No API layer yet; add one later with codz setup' },
478
+ ],
479
+ checkName: (n) => (/^[a-z][a-z0-9-]*$/.test(n) ? null : 'Use lowercase letters, digits and dashes, starting with a letter (for example shop-app); the app is then called ShopApp.'),
480
+ needsOrg: true,
481
+ options: [],
482
+ // Xcode has no command-line project creator; codz writes the App template itself.
483
+ creator: () => null,
484
+ skeleton: iosSkeleton,
485
+ install: () => null,
486
+ framework: 'ios',
487
+ },
488
+ ];
489
+ export function projectType(id) {
490
+ const t = PROJECT_TYPES.find((x) => x.id === id);
491
+ if (!t)
492
+ throw new ToolError('bad_type', `Unknown project type "${id}".`, `Types: ${PROJECT_TYPES.map((x) => x.id).join(', ')}`);
493
+ return t;
494
+ }
495
+ /** What create will do, without doing it (--dry-run). */
496
+ export function describeCreate(o) {
497
+ const t = projectType(o.type);
498
+ const dir = resolve(o.parent, o.name);
499
+ const c = t.creator(o);
500
+ const steps = [`Create ${dir}`, o.offline || !c ? `Write a minimal ${t.label} project (codz's own files)` : `Run ${c.label}: ${c.cmd} ${c.args.join(' ')} (falls back to codz's minimal project if it is not available)`];
501
+ if (o.architecture !== 'none')
502
+ steps.push(`Set up the ${t.architectures.find((a) => a.id === o.architecture)?.label ?? o.architecture} architecture with base URL ${o.baseUrl || 'https://api.example.com'}`);
503
+ steps.push('Write codz.config.yaml');
504
+ if (o.primary)
505
+ steps.push(`Set the theme's primary color to ${o.primary}`);
506
+ if (o.install !== false)
507
+ steps.push(`Install dependencies (${t.install(o)?.label ?? 'none'})`);
508
+ if (o.git !== false)
509
+ steps.push('git init and a first commit');
510
+ return steps;
511
+ }
512
+ function validate(o, t) {
513
+ const bad = t.checkName(o.name);
514
+ if (bad)
515
+ throw new ToolError('bad_name', `"${o.name}" cannot be a ${t.label} project name. ${bad}`);
516
+ if (!t.architectures.some((a) => a.id === o.architecture))
517
+ throw new ToolError('bad_args', `Unknown architecture "${o.architecture}" for ${t.label}.`, `Choose one of: ${t.architectures.map((a) => a.id).join(', ')}`);
518
+ if (t.needsOrg && !/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/.test(o.org))
519
+ throw new ToolError('bad_org', `"${o.org}" is not an organization id.`, 'Use reverse-domain form, for example com.example');
520
+ if (o.baseUrl)
521
+ o = { ...o, baseUrl: checkBaseUrl(o.baseUrl) };
522
+ if (o.primary && !/^#?[0-9a-fA-F]{6}$/.test(o.primary))
523
+ throw new ToolError('bad_color', `"${o.primary}" is not a #rrggbb color.`);
524
+ }
525
+ function run(cmd, args, cwd) {
526
+ const r = runTool(cmd, args, { cwd, timeout: 15 * 60_000 });
527
+ const missing = toolMissing(r);
528
+ return { ok: r.status === 0, missing, output: `${r.stdout ?? ''}${r.stderr ?? ''}`.trim() };
529
+ }
530
+ export async function createProject(o, log = () => { }) {
531
+ const t = projectType(o.type);
532
+ validate(o, t);
533
+ const parent = resolve(o.parent);
534
+ const dir = join(parent, o.name);
535
+ if (existsSync(dir) && readdirSync(dir).length)
536
+ throw new ToolError('dir_not_empty', `${dir} already exists and is not empty.`, 'Pick another name, or remove that folder.');
537
+ mkdirSync(parent, { recursive: true });
538
+ const steps = [];
539
+ const warnings = [];
540
+ // 1. The project itself.
541
+ let created = 'skeleton';
542
+ const creator = o.offline ? null : t.creator(o);
543
+ if (creator) {
544
+ log(`Running ${creator.label}...`);
545
+ const r = run(creator.cmd, creator.args, parent);
546
+ if (r.ok) {
547
+ created = 'creator';
548
+ steps.push(`Created ${dir} with ${creator.label}`);
549
+ }
550
+ else {
551
+ warnings.push(r.missing ? `${creator.label} is not installed, so codz wrote a minimal ${t.label} project instead.` : `${creator.label} failed (${r.output.split('\n').slice(-1)[0] || 'no output'}), so codz wrote a minimal ${t.label} project instead.`);
552
+ }
553
+ }
554
+ if (created === 'skeleton') {
555
+ for (const [path, content] of t.skeleton(o)) {
556
+ mkdirSync(dirname(join(dir, path)), { recursive: true });
557
+ writeFileSync(join(dir, path), content);
558
+ }
559
+ steps.push(`Created ${dir} (codz's minimal ${t.label} project)`);
560
+ if (t.id === 'react-native')
561
+ warnings.push(o.flavor === 'bare' ? `There are no android/ or ios/ folders yet. With network access, create the project with the React Native CLI instead (codz create without --offline), or copy them from a fresh npx @react-native-community/cli init.` : 'Expo creates the android/ and ios/ folders when you need them: npx expo prebuild (or just run npx expo start with Expo Go).');
562
+ if (t.id === 'android') {
563
+ if (addGradleWrapper(dir))
564
+ steps.push('Added the Gradle wrapper (gradlew)');
565
+ else
566
+ warnings.push(`Gradle is not installed here, so there is no gradlew yet. Open the project in Android Studio (it adds the wrapper), or run gradle wrapper in ${o.name}/.`);
567
+ steps.push(`Package ${androidPackage(o.org, o.name)}`);
568
+ }
569
+ if (t.id === 'flutter')
570
+ warnings.push(`There are no android/ or ios/ folders yet. With Flutter installed, run this in ${o.name}/ to add them: flutter create --org ${o.org} --project-name ${o.name} .`);
571
+ }
572
+ // 2. Architecture and config.
573
+ const d = await detectProject(dir, {});
574
+ const adapter = ADAPTERS[t.framework];
575
+ if (o.architecture !== 'none') {
576
+ const { changeSet, plan } = planSetup(dir, adapter, d.map, { architecture: o.architecture, baseUrl: o.baseUrl });
577
+ applyChangeSet(dir, changeSet, { allowDirty: true, verify: false, format: false, adapter });
578
+ steps.push(...plan.summary.filter((s) => !/^Save conventions/.test(s)));
579
+ warnings.push(...plan.warnings);
580
+ }
581
+ else {
582
+ writeInitFiles(dir, initFiles(dir, adapter, d.map.conventions), false);
583
+ }
584
+ steps.push('Wrote codz.config.yaml');
585
+ // 2b. The first screen: a demo in the architecture just set up, instead of the template's counter.
586
+ if (o.architecture !== 'none') {
587
+ if (t.id === 'flutter' && o.architecture === 'getx') {
588
+ // The GetX module (binding, controller, view and its route) comes from module add.
589
+ const { planModuleAdd } = await import("./module.js");
590
+ const mod = await planModuleAdd(dir, adapter, { name: 'home', initial: true });
591
+ applyChangeSet(dir, mod, { allowDirty: true, verify: false, format: false, adapter });
592
+ }
593
+ const demo = planDemo({ dir, type: t.id, architecture: o.architecture, name: o.name, androidPackage: t.id === 'android' ? androidPackage(o.org, o.name) : undefined, iosApp: t.id === 'ios' ? iosAppName(o.name) : undefined });
594
+ if (demo) {
595
+ const cs = buildChangeSet(dir, 'create', { files: demo.files, summary: demo.summary, warnings: [], generated: [], rewrites: demo.rewrites, ...(demo.deletes ? { deletes: new Set(demo.deletes) } : {}) });
596
+ applyChangeSet(dir, cs, { allowDirty: true, verify: false, format: false, adapter });
597
+ steps.push(...demo.summary);
598
+ }
599
+ }
600
+ // 3. Theme.
601
+ if (o.primary && t.id === 'android')
602
+ steps.push(`Theme: primary color ${o.primary}`);
603
+ else if (o.primary) {
604
+ const cs = await planThemeSet(dir, adapter, [{ name: 'primary', value: o.primary.startsWith('#') ? o.primary : `#${o.primary}` }]);
605
+ applyChangeSet(dir, cs, { allowDirty: true, verify: false, format: false, adapter });
606
+ steps.push(`Theme: primary color ${o.primary}`);
607
+ warnings.push(...cs.warnings);
608
+ }
609
+ // 4. Dependencies.
610
+ if (o.install !== false) {
611
+ const cmd = t.install(o);
612
+ if (cmd) {
613
+ log(`Running ${cmd.label}...`);
614
+ const r = run(cmd.cmd, cmd.args, dir);
615
+ if (r.ok)
616
+ steps.push(`Installed dependencies (${cmd.label})`);
617
+ else
618
+ warnings.push(`${cmd.label} ${r.missing ? 'is not available' : 'failed'}; run it yourself in ${o.name}/.`);
619
+ }
620
+ }
621
+ // 5. Git.
622
+ if (o.git !== false) {
623
+ const g = (...a) => run('git', a, dir);
624
+ if (existsSync(join(dir, '.git')) || g('init', '-q').ok) {
625
+ g('add', '-A');
626
+ // Your own git identity when one is set; a neutral one only when git has none.
627
+ const identity = g('config', 'user.email').ok ? [] : ['-c', 'user.name=codz', '-c', 'user.email=codz@localhost'];
628
+ const c = g(...identity, 'commit', '-qm', `Create ${o.name} with codz (${t.label}, ${o.architecture})`);
629
+ if (c.ok)
630
+ steps.push('git init and a first commit');
631
+ else
632
+ warnings.push('git init worked, but the first commit failed; commit yourself.');
633
+ }
634
+ else
635
+ warnings.push('git is not available, so the project is not a repository yet.');
636
+ }
637
+ return { dir, type: t.id, architecture: o.architecture, created, steps, warnings };
638
+ }