codz-cli 0.1.0 → 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.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  A project-aware CLI that learns how your project is built, then makes the same structural change the same way every time. It runs offline, never uploads code, shows a diff before writing anything, and can undo every change.
4
4
 
5
- Adapters for **Next.js**, **Flutter**, **React Native** and **Android (Kotlin)**: new projects (`codz create`), API endpoints (add, import, update, rename, remove), screens and forms, routes, mocks, tests, locales, theme values and project health checks, all with a diff first, `--plan`/`apply`, `undo` and `--json`. MIT licensed.
5
+ Adapters for **Next.js**, **React (Vite)**, **Flutter**, **React Native**, **Android (Kotlin)** and **iOS (SwiftUI)**: new projects (`codz create`, opening on a demo screen in the architecture you pick), API endpoints (add, import, update, rename, remove), screens and forms, routes, mocks, tests, locales, theme values and project health checks, all with a diff first, `--plan`/`apply`, `undo` and `--json`. MIT licensed.
6
6
 
7
7
  ## Requirements
8
8
 
@@ -75,9 +75,11 @@ codz create shop-app --type android --architecture retrofit-hilt --org com.acme
75
75
  codz create shop-app --type react-native --flavor expo --architecture axios-react-query
76
76
  codz create shop_app --type flutter --architecture riverpod --primary "#0F766E"
77
77
  codz create shop-app --type nextjs --architecture fetch-helper --tailwind --dry-run
78
+ codz create shop-web --type react --architecture axios-react-query
79
+ codz create shop-app --type ios --architecture urlsession --org com.acme
78
80
  ```
79
81
 
80
- `create` asks for the project type (Flutter, Next.js, React Native, Android), then the architecture for that type, the project name, the organization id (for app ids and packages), the API base URL and the type's own options (Tailwind for Next.js, Expo or the React Native CLI). It runs the platform's own creator (`flutter create`, `create-next-app`, `create-expo-app`, the React Native CLI), applies the architecture with the same `setup` as below, writes `codz.config.yaml`, sets the theme color, installs dependencies and makes the first git commit. Without the platform tool, or with `--offline`, codz writes a minimal project of its own instead. Android has no command-line creator, so codz always writes Android Studio's Empty Activity project itself (Kotlin, Jetpack Compose, a version catalog, Navigation Compose with a Home screen) and adds the Gradle wrapper when Gradle is installed. `--dry-run` lists the steps; `--no-install` and `--no-git` skip those steps.
82
+ `create` asks for the project type (Flutter, Next.js, React (Vite), React Native, Android, iOS), then the architecture for that type, the project name, the organization id (for app ids and packages), the API base URL and the type's own options (Tailwind for Next.js, Expo or the React Native CLI). It runs the platform's own creator (`flutter create`, `create-next-app`, `create-expo-app`, the React Native CLI), applies the architecture with the same `setup` as below, writes `codz.config.yaml`, sets the theme color, installs dependencies and makes the first git commit. Without the platform tool, or with `--offline`, codz writes a minimal project of its own instead. Android has no command-line creator, so codz always writes Android Studio's Empty Activity project itself (Kotlin, Jetpack Compose, a version catalog, Navigation Compose with a Home screen) and adds the Gradle wrapper. React (Vite) and iOS also use built-in templates: Vite's react-ts project, and Xcode 16's SwiftUI App project (a synchronized source folder, so files codz adds are part of the app without editing the project file). Every project opens on a demo task list written in the chosen architecture (a Riverpod notifier, a GetX module, a Cubit, a Hilt or Koin ViewModel, a TanStack Query hook, an ObservableObject...), on sample data, so you start from a working example of the pattern; `codz feature add` and `codz api add` replace the sample data with your API. `--dry-run` lists the steps; `--no-install` and `--no-git` skip those steps.
81
83
 
82
84
  | Type | Architectures |
83
85
  | --- | --- |
@@ -77,7 +77,7 @@ export const FLAGS = {
77
77
  // setup / init
78
78
  architecture: { value: '<id>', desc: 'Flutter: getx, riverpod, bloc, get_it. Next.js: fetch-helper, plain-fetch. React Native and React: fetch-helper, axios, axios-react-query. Android: retrofit-hilt, retrofit-koin, retrofit. iOS: urlsession' },
79
79
  // create
80
- type: { value: '<type>', desc: 'Project type: flutter, nextjs, react-native, android', choices: ['flutter', 'nextjs', 'react-native', 'android'] },
80
+ type: { value: '<type>', desc: 'Project type: flutter, nextjs, react, react-native, android, ios', choices: ['flutter', 'nextjs', 'react', 'react-native', 'android', 'ios'] },
81
81
  flavor: { value: '<expo|bare>', desc: 'React Native: Expo (default) or the React Native CLI', choices: ['expo', 'bare'] },
82
82
  org: { value: '<com.example>', desc: 'Organization in reverse-domain form, for app ids (default com.example)' },
83
83
  tailwind: { desc: 'Next.js: use Tailwind CSS' },
@@ -112,7 +112,7 @@ export const FLAGS = {
112
112
  export const GLOBAL_FLAGS = ['json', 'cwd', 'root', 'no-color', 'help', 'h', 'version', 'v'];
113
113
  export const CHANGE_FLAGS = ['dry-run', 'plan', 'yes', 'y', 'allow-dirty', 'no-format', 'no-verify', 'commit', 'commit-message', 'open'];
114
114
  export const COMMAND_LIST = [
115
- { group: 'Start', cmd: 'create', usage: '[name] [--type <type>] [--architecture <id>]', summary: 'Create a new project and set it up: pick the type, architecture, name and options, then codz runs the platform creator and applies the architecture', flags: ['type', 'architecture', 'base-url', 'org', 'tailwind', 'flavor', 'primary', 'offline', 'no-install', 'no-git', 'dry-run', 'yes', 'y'], notes: ['Asks for anything not given as an option. Architectures: Flutter riverpod, getx, bloc, get_it, none; Next.js fetch-helper, plain-fetch, none; React Native fetch-helper, axios-react-query, axios, none (--flavor expo or bare); Android retrofit-hilt, retrofit-koin, retrofit, none (Kotlin, Jetpack Compose, version catalog). Without the platform creator (flutter, npx) or with --offline, codz writes a minimal project itself; Android always uses the built-in template (the Android Studio Empty Activity project plus Navigation Compose).'] },
115
+ { group: 'Start', cmd: 'create', usage: '[name] [--type <type>] [--architecture <id>]', summary: 'Create a new project and set it up: pick the type, architecture, name and options, then codz runs the platform creator and applies the architecture', flags: ['type', 'architecture', 'base-url', 'org', 'tailwind', 'flavor', 'primary', 'offline', 'no-install', 'no-git', 'dry-run', 'yes', 'y'], notes: ['Asks for anything not given as an option. Architectures: Flutter riverpod, getx, bloc, get_it, none; Next.js fetch-helper, plain-fetch, none; React Native fetch-helper, axios-react-query, axios, none (--flavor expo or bare); Android retrofit-hilt, retrofit-koin, retrofit, none (Kotlin, Jetpack Compose, version catalog); React (Vite) fetch-helper, axios, axios-react-query, none; iOS urlsession, none (SwiftUI, Xcode 16 project). The app opens on a demo screen written in the chosen architecture, on sample data. Without the platform creator (flutter, npx) or with --offline, codz writes a minimal project itself; Android, React (Vite) and iOS always use the built-in templates.'] },
116
116
  { group: 'Start', cmd: 'init', usage: '[--root <folder>] [--force]', summary: 'Detect the framework, scan, write codz.config.yaml; offers to set up an API layer when there is none', flags: ['force', 'architecture', 'base-url', 'yes', 'y'] },
117
117
  { group: 'Start', cmd: 'setup', usage: '[--architecture <id>] [--base-url <url>]', summary: 'Create the API layer for a fresh project', flags: ['architecture', 'base-url'], change: true },
118
118
  { group: 'Start', cmd: 'explain', usage: '[--rescan]', summary: 'Show routes, endpoints, models, locales and conventions', flags: ['rescan'] },
@@ -13,11 +13,12 @@ import { existsSync, mkdirSync, readdirSync, writeFileSync } from 'node:fs';
13
13
  import { dirname, join, resolve } from 'node:path';
14
14
  import { addGradleWrapper, androidPackage, androidSkeleton } from "./androidSkeleton.js";
15
15
  import { ADAPTERS } from "../adapters/index.js";
16
- import { applyChangeSet } from "../core/changeset.js";
16
+ import { applyChangeSet, buildChangeSet } from "../core/changeset.js";
17
17
  import { ToolError } from "../core/types.js";
18
18
  import { DART_RESERVED } from "../input/naming.js";
19
19
  import { detectProject, initFiles, writeInitFiles } from "./init.js";
20
20
  import { planSetup } from "./setup.js";
21
+ import { planDemo } from "./createDemo.js";
21
22
  import { planThemeSet } from "./theme.js";
22
23
  import { runTool, toolMissing } from "../core/proc.js";
23
24
  const NONE = { id: 'none', label: 'None', description: 'No API layer yet; set one up later with codz setup' };
@@ -139,6 +140,234 @@ function reactNativeSkeleton(o) {
139
140
  }
140
141
  return files;
141
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
+ }
142
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).');
143
372
  export const PROJECT_TYPES = [
144
373
  {
@@ -220,6 +449,42 @@ export const PROJECT_TYPES = [
220
449
  install: () => null,
221
450
  framework: 'android',
222
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
+ },
223
488
  ];
224
489
  export function projectType(id) {
225
490
  const t = PROJECT_TYPES.find((x) => x.id === id);
@@ -317,6 +582,21 @@ export async function createProject(o, log = () => { }) {
317
582
  writeInitFiles(dir, initFiles(dir, adapter, d.map.conventions), false);
318
583
  }
319
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
+ }
320
600
  // 3. Theme.
321
601
  if (o.primary && t.id === 'android')
322
602
  steps.push(`Theme: primary color ${o.primary}`);