codz-cli 0.1.0 → 0.1.3
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 +4 -2
- package/dist/core/commands.js +2 -2
- package/dist/skills/create.js +281 -1
- package/dist/skills/createDemo.js +1026 -0
- package/docs/COMMANDS.md +2 -2
- package/package.json +2 -2
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 **
|
|
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
|
|
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
|
| --- | --- |
|
package/dist/core/commands.js
CHANGED
|
@@ -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
|
|
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'] },
|
package/dist/skills/create.js
CHANGED
|
@@ -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}`);
|