optikit 1.4.0 โ†’ 1.4.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/CHANGELOG.md CHANGED
@@ -12,6 +12,22 @@ We follow **Semantic Versioning (SemVer)**:
12
12
 
13
13
  ---
14
14
 
15
+ ### ๐Ÿ›  [1.4.2] - Fix bump-ios Overwriting Manually Set iOS Version
16
+
17
+ - ๐Ÿ”ด Fixed `bump-ios` / `tf` overwriting a manually set `MARKETING_VERSION` in `project.pbxproj` with the stale version from `pubspec.yaml`
18
+ - ๐Ÿ†• Added `getCurrentIosMarketingVersion()` helper that reads `MARKETING_VERSION` directly from `project.pbxproj` (falls back to `pubspec.yaml` if iOS file is missing)
19
+ - ๐Ÿ”„ `bumpIosBuildOnly()` now uses the iOS marketing version as source of truth instead of `pubspec.yaml`, so calling `flutter-update-version --app-version X --ios-build N` followed by `bump-ios` / `tf` correctly preserves the manually set version
20
+
21
+ ---
22
+
23
+ ### ๐Ÿ›  [1.4.1] - Fix Generate Module Command
24
+
25
+ - ๐Ÿ”ด Fixed `generate module` routing to repo handler instead of module handler
26
+ - ๐Ÿ”„ Restructured `generate` as parent command with `module` and `repo` subcommands
27
+ - ๐Ÿงน Removed stale `repo/` directory from module scaffolding structure
28
+
29
+ ---
30
+
15
31
  ### ๐ŸŒŸ [1.4.0] - Interactive Repo Picker & Module Cleanup
16
32
 
17
33
  - ๐Ÿ“‚ Interactive arrow-key directory picker for `gen repo` โ€” browse, open, go back, or create folders
@@ -6,30 +6,38 @@ import { runDoctor } from "./doctor.js";
6
6
  import { showStatus } from "./status.js";
7
7
  export const projectCommands = [
8
8
  {
9
- command: "generate module <moduleName>",
10
- aliases: ["gen module"],
11
- describe: "Generate a module with structure",
9
+ command: "generate",
10
+ aliases: ["gen"],
11
+ describe: "Generate module or repository scaffolding",
12
12
  builder: (yargs) => {
13
13
  return yargs
14
- .positional("moduleName", { describe: "The name of the module to generate", type: "string" })
15
- .option("with-route", { alias: "r", type: "boolean", default: false, description: "Also register a route in app_router.dart" });
14
+ .command({
15
+ command: "module <moduleName>",
16
+ describe: "Generate a module with full BLoC structure",
17
+ builder: (y) => {
18
+ return y
19
+ .positional("moduleName", { describe: "The name of the module to generate", type: "string" })
20
+ .option("with-route", { alias: "r", type: "boolean", default: false, description: "Also register a route in app_router.dart" });
21
+ },
22
+ handler: (argv) => {
23
+ const moduleName = argv.moduleName;
24
+ generateModule(moduleName);
25
+ if (argv.withRoute) {
26
+ addRoute(moduleName);
27
+ }
28
+ },
29
+ })
30
+ .command({
31
+ command: "repo <moduleName>",
32
+ describe: "Generate a repository file for an existing module",
33
+ builder: (y) => {
34
+ return y.positional("moduleName", { describe: "The module name", type: "string" });
35
+ },
36
+ handler: async (argv) => { await generateRepoModule(argv.moduleName); },
37
+ })
38
+ .demandCommand(1, "Specify a subcommand: module or repo");
16
39
  },
17
- handler: (argv) => {
18
- const moduleName = argv.moduleName;
19
- generateModule(moduleName);
20
- if (argv.withRoute) {
21
- addRoute(moduleName);
22
- }
23
- },
24
- },
25
- {
26
- command: "generate repo <moduleName>",
27
- aliases: ["gen repo"],
28
- describe: "Generate a repository file for an existing module",
29
- builder: (yargs) => {
30
- return yargs.positional("moduleName", { describe: "The module name", type: "string" });
31
- },
32
- handler: async (argv) => { await generateRepoModule(argv.moduleName); },
40
+ handler: () => { },
33
41
  },
34
42
  {
35
43
  command: "add-route <moduleName>",
@@ -1,7 +1,7 @@
1
1
  import { createInterface } from "readline";
2
2
  import { validateFlutterProject } from "../../utils/validators/validation.js";
3
3
  import { LoggerHelpers } from "../../utils/services/logger.js";
4
- import { getCurrentVersion, incrementVersion, formatVersion, getCurrentIosBuildNumber } from "../../utils/helpers/version.js";
4
+ import { getCurrentVersion, incrementVersion, formatVersion, getCurrentIosBuildNumber, getCurrentIosMarketingVersion } from "../../utils/helpers/version.js";
5
5
  import { handleCommandError } from "../../utils/helpers/error.js";
6
6
  import { updateFlutterVersion } from "./update.js";
7
7
  import chalk from "chalk";
@@ -79,22 +79,22 @@ async function bumpIosBuildOnly(skipConfirm = false) {
79
79
  }
80
80
  try {
81
81
  const current = getCurrentVersion();
82
- const currentVersionString = `${current.major}.${current.minor}.${current.patch}`;
82
+ const iosVersionString = getCurrentIosMarketingVersion();
83
83
  const currentIosBuild = getCurrentIosBuildNumber();
84
84
  const nextIosBuild = currentIosBuild + 1;
85
- LoggerHelpers.info(`Current version: ${formatVersion(current)}`);
85
+ LoggerHelpers.info(`Current iOS version: ${iosVersionString} (build ${currentIosBuild})`);
86
86
  LoggerHelpers.info("Incrementing iOS build number only (for TestFlight)...");
87
87
  console.log(chalk.cyan("\nBuild number changes:"));
88
- console.log(chalk.gray(" Version:"), chalk.white(`${currentVersionString} (unchanged)`));
88
+ console.log(chalk.gray(" Version:"), chalk.white(`${iosVersionString} (unchanged)`));
89
89
  console.log(chalk.gray(" Android:"), chalk.white(`${current.buildNumber} (unchanged)`));
90
90
  console.log(chalk.gray(" iOS:"), chalk.white(`${currentIosBuild} โ†’ ${nextIosBuild}`), chalk.green("(incremented)"));
91
- showDiffPreview(currentVersionString, currentVersionString, current.buildNumber, "", currentIosBuild, nextIosBuild);
91
+ showDiffPreview(iosVersionString, iosVersionString, current.buildNumber, "", currentIosBuild, nextIosBuild);
92
92
  const confirmed = await confirmAction("Proceed?", skipConfirm);
93
93
  if (!confirmed) {
94
94
  LoggerHelpers.info("Bump cancelled.");
95
95
  return;
96
96
  }
97
- await updateFlutterVersion(currentVersionString, "", nextIosBuild.toString());
97
+ await updateFlutterVersion(iosVersionString, "", nextIosBuild.toString());
98
98
  LoggerHelpers.success(`iOS build number incremented to ${nextIosBuild}`);
99
99
  }
100
100
  catch (error) {
package/dist/constants.js CHANGED
@@ -44,7 +44,7 @@ export const BACKUP_CONFIG = {
44
44
  };
45
45
  // Module generation
46
46
  export const MODULE_STRUCTURE = {
47
- DIRECTORIES: ["bloc", "event", "state", "screen", "import", "factory", "repo"],
47
+ DIRECTORIES: ["bloc", "event", "state", "screen", "import", "factory"],
48
48
  NAME_PATTERN: /^[a-z0-9_]+$/,
49
49
  };
50
50
  // Flutter commands
@@ -2,7 +2,7 @@ import fs from "fs";
2
2
  import path from "path";
3
3
  import { LoggerHelpers } from "../services/logger.js";
4
4
  import { PROJECT_PATHS } from "../../constants.js";
5
- export { parseVersion, incrementVersion, getCurrentVersion, getCurrentIosBuildNumber };
5
+ export { parseVersion, incrementVersion, getCurrentVersion, getCurrentIosBuildNumber, getCurrentIosMarketingVersion };
6
6
  /**
7
7
  * Parses a version string in format "X.Y.Z+B"
8
8
  * @param versionString - Version string (e.g., "1.2.3+45")
@@ -80,6 +80,31 @@ export function getNextBuildNumber() {
80
80
  const current = getCurrentVersion();
81
81
  return current.buildNumber + 1;
82
82
  }
83
+ /**
84
+ * Gets the current iOS marketing version (MARKETING_VERSION) from project.pbxproj.
85
+ * Falls back to pubspec.yaml version when the iOS file is not found.
86
+ */
87
+ function getCurrentIosMarketingVersion() {
88
+ try {
89
+ const projectPbxProjPath = path.join(process.cwd(), PROJECT_PATHS.IOS_PROJECT_PBXPROJ);
90
+ if (!fs.existsSync(projectPbxProjPath)) {
91
+ const fallback = getCurrentVersion();
92
+ return `${fallback.major}.${fallback.minor}.${fallback.patch}`;
93
+ }
94
+ const projectContent = fs.readFileSync(projectPbxProjPath, 'utf8');
95
+ const match = projectContent.match(/MARKETING_VERSION\s*=\s*([^;]+);/);
96
+ if (!match) {
97
+ const fallback = getCurrentVersion();
98
+ return `${fallback.major}.${fallback.minor}.${fallback.patch}`;
99
+ }
100
+ return match[1].trim();
101
+ }
102
+ catch (error) {
103
+ LoggerHelpers.warning(`Error reading iOS marketing version: ${error instanceof Error ? error.message : error}. Falling back to pubspec.yaml.`);
104
+ const fallback = getCurrentVersion();
105
+ return `${fallback.major}.${fallback.minor}.${fallback.patch}`;
106
+ }
107
+ }
83
108
  /**
84
109
  * Gets the current iOS build number from project.pbxproj
85
110
  * @returns Current iOS build number, or 1 if not found
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "optikit",
3
- "version": "1.4.0",
3
+ "version": "1.4.2",
4
4
  "description": "OptiKit CLI",
5
5
  "main": "src/cli.ts",
6
6
  "type": "module",
@@ -8,28 +8,36 @@ import { showStatus } from "./status.js";
8
8
 
9
9
  export const projectCommands: CommandModule[] = [
10
10
  {
11
- command: "generate module <moduleName>",
12
- aliases: ["gen module"],
13
- describe: "Generate a module with structure",
11
+ command: "generate",
12
+ aliases: ["gen"],
13
+ describe: "Generate module or repository scaffolding",
14
14
  builder: (yargs) => {
15
15
  return yargs
16
- .positional("moduleName", { describe: "The name of the module to generate", type: "string" as const })
17
- .option("with-route", { alias: "r", type: "boolean", default: false, description: "Also register a route in app_router.dart" });
16
+ .command({
17
+ command: "module <moduleName>",
18
+ describe: "Generate a module with full BLoC structure",
19
+ builder: (y) => {
20
+ return y
21
+ .positional("moduleName", { describe: "The name of the module to generate", type: "string" as const })
22
+ .option("with-route", { alias: "r", type: "boolean", default: false, description: "Also register a route in app_router.dart" });
23
+ },
24
+ handler: (argv) => {
25
+ const moduleName = argv.moduleName as string;
26
+ generateModule(moduleName);
27
+ if (argv.withRoute) { addRoute(moduleName); }
28
+ },
29
+ })
30
+ .command({
31
+ command: "repo <moduleName>",
32
+ describe: "Generate a repository file for an existing module",
33
+ builder: (y) => {
34
+ return y.positional("moduleName", { describe: "The module name", type: "string" as const });
35
+ },
36
+ handler: async (argv) => { await generateRepoModule(argv.moduleName as string); },
37
+ })
38
+ .demandCommand(1, "Specify a subcommand: module or repo");
18
39
  },
19
- handler: (argv) => {
20
- const moduleName = argv.moduleName as string;
21
- generateModule(moduleName);
22
- if (argv.withRoute) { addRoute(moduleName); }
23
- },
24
- },
25
- {
26
- command: "generate repo <moduleName>",
27
- aliases: ["gen repo"],
28
- describe: "Generate a repository file for an existing module",
29
- builder: (yargs) => {
30
- return yargs.positional("moduleName", { describe: "The module name", type: "string" as const });
31
- },
32
- handler: async (argv) => { await generateRepoModule(argv.moduleName as string); },
40
+ handler: () => { /* handled by subcommands */ },
33
41
  },
34
42
  {
35
43
  command: "add-route <moduleName>",
@@ -6,7 +6,8 @@ import {
6
6
  incrementVersion,
7
7
  formatVersion,
8
8
  getNextBuildNumber,
9
- getCurrentIosBuildNumber
9
+ getCurrentIosBuildNumber,
10
+ getCurrentIosMarketingVersion
10
11
  } from "../../utils/helpers/version.js";
11
12
  import { handleCommandError } from "../../utils/helpers/error.js";
12
13
  import { updateFlutterVersion } from "./update.js";
@@ -122,19 +123,19 @@ async function bumpIosBuildOnly(skipConfirm: boolean = false): Promise<void> {
122
123
 
123
124
  try {
124
125
  const current = getCurrentVersion();
125
- const currentVersionString = `${current.major}.${current.minor}.${current.patch}`;
126
+ const iosVersionString = getCurrentIosMarketingVersion();
126
127
  const currentIosBuild = getCurrentIosBuildNumber();
127
128
  const nextIosBuild = currentIosBuild + 1;
128
129
 
129
- LoggerHelpers.info(`Current version: ${formatVersion(current)}`);
130
+ LoggerHelpers.info(`Current iOS version: ${iosVersionString} (build ${currentIosBuild})`);
130
131
  LoggerHelpers.info("Incrementing iOS build number only (for TestFlight)...");
131
132
 
132
133
  console.log(chalk.cyan("\nBuild number changes:"));
133
- console.log(chalk.gray(" Version:"), chalk.white(`${currentVersionString} (unchanged)`));
134
+ console.log(chalk.gray(" Version:"), chalk.white(`${iosVersionString} (unchanged)`));
134
135
  console.log(chalk.gray(" Android:"), chalk.white(`${current.buildNumber} (unchanged)`));
135
136
  console.log(chalk.gray(" iOS:"), chalk.white(`${currentIosBuild} โ†’ ${nextIosBuild}`), chalk.green("(incremented)"));
136
137
 
137
- showDiffPreview(currentVersionString, currentVersionString, current.buildNumber, "", currentIosBuild, nextIosBuild);
138
+ showDiffPreview(iosVersionString, iosVersionString, current.buildNumber, "", currentIosBuild, nextIosBuild);
138
139
 
139
140
  const confirmed = await confirmAction("Proceed?", skipConfirm);
140
141
  if (!confirmed) {
@@ -142,7 +143,7 @@ async function bumpIosBuildOnly(skipConfirm: boolean = false): Promise<void> {
142
143
  return;
143
144
  }
144
145
 
145
- await updateFlutterVersion(currentVersionString, "", nextIosBuild.toString());
146
+ await updateFlutterVersion(iosVersionString, "", nextIosBuild.toString());
146
147
 
147
148
  LoggerHelpers.success(`iOS build number incremented to ${nextIosBuild}`);
148
149
 
package/src/constants.ts CHANGED
@@ -48,7 +48,7 @@ export const BACKUP_CONFIG = {
48
48
 
49
49
  // Module generation
50
50
  export const MODULE_STRUCTURE = {
51
- DIRECTORIES: ["bloc", "event", "state", "screen", "import", "factory", "repo"],
51
+ DIRECTORIES: ["bloc", "event", "state", "screen", "import", "factory"],
52
52
  NAME_PATTERN: /^[a-z0-9_]+$/,
53
53
  } as const;
54
54
 
@@ -3,7 +3,7 @@ import path from "path";
3
3
  import { LoggerHelpers } from "../services/logger.js";
4
4
  import { PROJECT_PATHS } from "../../constants.js";
5
5
 
6
- export { parseVersion, incrementVersion, getCurrentVersion, getCurrentIosBuildNumber, VersionInfo };
6
+ export { parseVersion, incrementVersion, getCurrentVersion, getCurrentIosBuildNumber, getCurrentIosMarketingVersion, VersionInfo };
7
7
 
8
8
  /**
9
9
  * Version information structure
@@ -109,6 +109,35 @@ export function getNextBuildNumber(): number {
109
109
  return current.buildNumber + 1;
110
110
  }
111
111
 
112
+ /**
113
+ * Gets the current iOS marketing version (MARKETING_VERSION) from project.pbxproj.
114
+ * Falls back to pubspec.yaml version when the iOS file is not found.
115
+ */
116
+ function getCurrentIosMarketingVersion(): string {
117
+ try {
118
+ const projectPbxProjPath = path.join(process.cwd(), PROJECT_PATHS.IOS_PROJECT_PBXPROJ);
119
+
120
+ if (!fs.existsSync(projectPbxProjPath)) {
121
+ const fallback = getCurrentVersion();
122
+ return `${fallback.major}.${fallback.minor}.${fallback.patch}`;
123
+ }
124
+
125
+ const projectContent = fs.readFileSync(projectPbxProjPath, 'utf8');
126
+ const match = projectContent.match(/MARKETING_VERSION\s*=\s*([^;]+);/);
127
+
128
+ if (!match) {
129
+ const fallback = getCurrentVersion();
130
+ return `${fallback.major}.${fallback.minor}.${fallback.patch}`;
131
+ }
132
+
133
+ return match[1].trim();
134
+ } catch (error) {
135
+ LoggerHelpers.warning(`Error reading iOS marketing version: ${error instanceof Error ? error.message : error}. Falling back to pubspec.yaml.`);
136
+ const fallback = getCurrentVersion();
137
+ return `${fallback.major}.${fallback.minor}.${fallback.patch}`;
138
+ }
139
+ }
140
+
112
141
  /**
113
142
  * Gets the current iOS build number from project.pbxproj
114
143
  * @returns Current iOS build number, or 1 if not found