@tolgamorf/env2op-cli 0.2.1 → 0.2.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
@@ -19,7 +19,7 @@ Or in a single command:
19
19
  brew install tolgamorf/tap/env2op-cli
20
20
  ```
21
21
 
22
- ### Package Managers (All Operating Systems)
22
+ ### Package Managers (macOS/Linux/Windows)
23
23
 
24
24
  #### Global installation
25
25
 
@@ -97,8 +97,10 @@ env2op .env.production Personal "MyApp" -f
97
97
  | `-f, --force` | Skip confirmation prompts |
98
98
  | `--dry-run` | Preview actions without executing |
99
99
  | `--secret` | Store all fields as 'password' type (default: 'text') |
100
- | `-h, --help` | Show help |
100
+ | `--verbose` | Show op CLI output |
101
+ | `--update` | Check for and install updates |
101
102
  | `-v, --version` | Show version |
103
+ | `-h, --help` | Show help |
102
104
 
103
105
  ## op2env (Pull)
104
106
 
@@ -131,8 +133,10 @@ op2env .env.tpl -f
131
133
  | `-o, --output` | Output .env path (default: template without `.tpl`) |
132
134
  | `-f, --force` | Overwrite without prompting |
133
135
  | `--dry-run` | Preview actions without executing |
134
- | `-h, --help` | Show help |
136
+ | `--verbose` | Show op CLI output |
137
+ | `--update` | Check for and install updates |
135
138
  | `-v, --version` | Show version |
139
+ | `-h, --help` | Show help |
136
140
 
137
141
  ## How It Works
138
142
 
package/dist/cli.js CHANGED
@@ -22,7 +22,7 @@ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports,
22
22
  var require_package = __commonJS((exports, module) => {
23
23
  module.exports = {
24
24
  name: "@tolgamorf/env2op-cli",
25
- version: "0.2.1",
25
+ version: "0.2.2",
26
26
  description: "Convert .env files to 1Password Secure Notes and generate templates for op inject/run",
27
27
  type: "module",
28
28
  main: "dist/index.js",
@@ -102,7 +102,7 @@ var require_package = __commonJS((exports, module) => {
102
102
  });
103
103
 
104
104
  // src/cli.ts
105
- import pc3 from "picocolors";
105
+ import pc5 from "picocolors";
106
106
 
107
107
  // src/commands/convert.ts
108
108
  import { basename, dirname, join } from "node:path";
@@ -788,6 +788,274 @@ async function runConvert(options) {
788
788
  }
789
789
  }
790
790
 
791
+ // src/commands/update.ts
792
+ import * as p4 from "@clack/prompts";
793
+ import pc4 from "picocolors";
794
+
795
+ // src/lib/package-manager.ts
796
+ var UPDATE_COMMANDS = {
797
+ homebrew: "brew upgrade tolgamorf/tap/env2op-cli",
798
+ npm: "npm update -g @tolgamorf/env2op-cli",
799
+ bun: "bun update -g @tolgamorf/env2op-cli",
800
+ pnpm: "pnpm update -g @tolgamorf/env2op-cli",
801
+ unknown: "npm update -g @tolgamorf/env2op-cli"
802
+ };
803
+ var DISPLAY_NAMES = {
804
+ homebrew: "Homebrew",
805
+ npm: "npm",
806
+ bun: "Bun",
807
+ pnpm: "pnpm",
808
+ unknown: "npm (default)"
809
+ };
810
+ function detectFromPath() {
811
+ const binPath = process.argv[1] ?? "";
812
+ if (binPath.includes("/Cellar/") || binPath.includes("/homebrew/") || binPath.includes("/opt/homebrew/") || binPath.includes("/home/linuxbrew/")) {
813
+ return "homebrew";
814
+ }
815
+ if (binPath.includes("/.bun/")) {
816
+ return "bun";
817
+ }
818
+ if (binPath.includes("/pnpm/") || binPath.includes("/.pnpm/")) {
819
+ return "pnpm";
820
+ }
821
+ if (binPath.includes("/node_modules/")) {
822
+ return "npm";
823
+ }
824
+ return null;
825
+ }
826
+ async function detectFromCommands() {
827
+ const brewResult = await exec("brew", ["list", "env2op-cli"], { verbose: false });
828
+ if (brewResult.exitCode === 0) {
829
+ return "homebrew";
830
+ }
831
+ return "npm";
832
+ }
833
+ async function detectPackageManager() {
834
+ const fromPath = detectFromPath();
835
+ if (fromPath) {
836
+ return {
837
+ type: fromPath,
838
+ updateCommand: UPDATE_COMMANDS[fromPath],
839
+ displayName: DISPLAY_NAMES[fromPath]
840
+ };
841
+ }
842
+ const fromCommands = await detectFromCommands();
843
+ return {
844
+ type: fromCommands,
845
+ updateCommand: UPDATE_COMMANDS[fromCommands],
846
+ displayName: DISPLAY_NAMES[fromCommands]
847
+ };
848
+ }
849
+
850
+ // src/lib/update.ts
851
+ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
852
+ import { homedir } from "node:os";
853
+ import { join as join2 } from "node:path";
854
+ var CACHE_DIR = join2(homedir(), ".env2op");
855
+ var CACHE_FILE = join2(CACHE_DIR, "update-check.json");
856
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
857
+ function getCliVersion() {
858
+ try {
859
+ const pkg2 = require_package();
860
+ return pkg2.version ?? "0.0.0";
861
+ } catch {
862
+ return "0.0.0";
863
+ }
864
+ }
865
+ function loadCache() {
866
+ try {
867
+ if (existsSync(CACHE_FILE)) {
868
+ const content = readFileSync(CACHE_FILE, "utf-8");
869
+ return JSON.parse(content);
870
+ }
871
+ } catch {}
872
+ return { lastCheck: 0, latestVersion: null };
873
+ }
874
+ function saveCache(cache) {
875
+ try {
876
+ if (!existsSync(CACHE_DIR)) {
877
+ mkdirSync(CACHE_DIR, { recursive: true });
878
+ }
879
+ writeFileSync2(CACHE_FILE, JSON.stringify(cache, null, 2));
880
+ } catch {}
881
+ }
882
+ function shouldCheckForUpdate(cache) {
883
+ const now = Date.now();
884
+ return now - cache.lastCheck > CHECK_INTERVAL_MS;
885
+ }
886
+ async function fetchLatestVersion() {
887
+ try {
888
+ const response = await fetch("https://registry.npmjs.org/@tolgamorf/env2op-cli/latest");
889
+ if (!response.ok)
890
+ return null;
891
+ const data = await response.json();
892
+ return data.version ?? null;
893
+ } catch {
894
+ return null;
895
+ }
896
+ }
897
+ function compareVersions(v1, v2) {
898
+ const parts1 = v1.split(".").map(Number);
899
+ const parts2 = v2.split(".").map(Number);
900
+ for (let i = 0;i < 3; i++) {
901
+ const p1 = parts1[i] || 0;
902
+ const p22 = parts2[i] || 0;
903
+ if (p1 < p22)
904
+ return -1;
905
+ if (p1 > p22)
906
+ return 1;
907
+ }
908
+ return 0;
909
+ }
910
+ async function checkForUpdate(forceCheck = false) {
911
+ const currentVersion = getCliVersion();
912
+ const cache = loadCache();
913
+ if (!forceCheck && !shouldCheckForUpdate(cache) && cache.latestVersion) {
914
+ const updateAvailable2 = compareVersions(currentVersion, cache.latestVersion) < 0;
915
+ const isSkipped2 = cache.skipVersion === cache.latestVersion;
916
+ return {
917
+ currentVersion,
918
+ latestVersion: cache.latestVersion,
919
+ updateAvailable: updateAvailable2,
920
+ isSkipped: isSkipped2,
921
+ fromCache: true
922
+ };
923
+ }
924
+ const latestVersion = await fetchLatestVersion();
925
+ saveCache({
926
+ ...cache,
927
+ lastCheck: Date.now(),
928
+ latestVersion
929
+ });
930
+ if (!latestVersion) {
931
+ return {
932
+ currentVersion,
933
+ latestVersion: null,
934
+ updateAvailable: false,
935
+ isSkipped: false,
936
+ fromCache: false
937
+ };
938
+ }
939
+ const updateAvailable = compareVersions(currentVersion, latestVersion) < 0;
940
+ const isSkipped = cache.skipVersion === latestVersion;
941
+ return {
942
+ currentVersion,
943
+ latestVersion,
944
+ updateAvailable,
945
+ isSkipped,
946
+ fromCache: false
947
+ };
948
+ }
949
+ async function performUpdate(pm) {
950
+ const packageManager = pm ?? await detectPackageManager();
951
+ try {
952
+ const [command, ...args] = packageManager.updateCommand.split(" ");
953
+ const result = await exec(command, args, { verbose: false });
954
+ if (result.exitCode !== 0) {
955
+ return {
956
+ success: false,
957
+ error: result.stderr || `Command exited with code ${result.exitCode}`
958
+ };
959
+ }
960
+ return { success: true };
961
+ } catch (error) {
962
+ const message = error instanceof Error ? error.message : String(error);
963
+ return { success: false, error: message };
964
+ }
965
+ }
966
+ function skipVersion(version) {
967
+ const cache = loadCache();
968
+ cache.skipVersion = version;
969
+ saveCache(cache);
970
+ }
971
+
972
+ // src/lib/update-prompts.ts
973
+ import * as p3 from "@clack/prompts";
974
+ import pc3 from "picocolors";
975
+ var S_BAR_START = "┌";
976
+ var S_BAR_END = "└";
977
+ function showUpdateNotification(result, cliName = "env2op") {
978
+ console.log();
979
+ console.log(`${pc3.gray(S_BAR_START)}${pc3.gray("─")} ${pc3.yellow("Update available:")} ${pc3.dim(result.currentVersion)} ${pc3.dim("→")} ${pc3.green(result.latestVersion)}`);
980
+ console.log(`${pc3.gray(S_BAR_END)}${pc3.gray("─")} Run ${pc3.cyan(`'${cliName} update'`)} to update`);
981
+ }
982
+ async function askToUpdate(result) {
983
+ const response = await p3.select({
984
+ message: "Would you like to update?",
985
+ options: [
986
+ { value: "update", label: "Update now", hint: "Download and install the latest version" },
987
+ { value: "later", label: "Remind me later", hint: "Ask again next time" },
988
+ { value: "skip", label: "Skip this version", hint: `Don't ask about ${result.latestVersion} again` }
989
+ ]
990
+ });
991
+ if (p3.isCancel(response)) {
992
+ return "later";
993
+ }
994
+ return response;
995
+ }
996
+ function showUpdateAvailable(result) {
997
+ p3.log.success(`Update available: ${pc3.dim(result.currentVersion)} ${pc3.dim("→")} ${pc3.green(result.latestVersion)}`);
998
+ }
999
+ function showUpToDate(currentVersion) {
1000
+ p3.log.success(`You're on the latest version ${pc3.green(`(${currentVersion})`)}`);
1001
+ }
1002
+ function showPackageManagerInfo(pm) {
1003
+ console.log();
1004
+ console.log(` ${pc3.dim("Detected:")} ${pm.displayName} installation`);
1005
+ console.log(` ${pc3.dim("Command:")} ${pc3.cyan(pm.updateCommand)}`);
1006
+ console.log();
1007
+ }
1008
+ function showUpdateSuccess(newVersion) {
1009
+ p3.log.success(`Updated to version ${pc3.green(newVersion)}`);
1010
+ p3.log.info("Please restart to use the new version.");
1011
+ }
1012
+ function showUpdateError(error, pm) {
1013
+ if (error) {
1014
+ p3.log.error(pc3.dim(error));
1015
+ }
1016
+ p3.log.info(`Try running manually: ${pc3.cyan(pm.updateCommand)}`);
1017
+ }
1018
+
1019
+ // src/commands/update.ts
1020
+ async function runUpdate(options) {
1021
+ const { force = false, cliName = "env2op" } = options;
1022
+ p4.intro(pc4.bgCyan(pc4.black(` ${cliName} update `)));
1023
+ const spinner4 = p4.spinner();
1024
+ spinner4.start("Checking for updates...");
1025
+ const result = await checkForUpdate(true);
1026
+ spinner4.stop("Checked for updates");
1027
+ if (!result.updateAvailable || !result.latestVersion) {
1028
+ showUpToDate(result.currentVersion);
1029
+ return;
1030
+ }
1031
+ showUpdateAvailable(result);
1032
+ const pm = await detectPackageManager();
1033
+ showPackageManagerInfo(pm);
1034
+ if (!force) {
1035
+ const choice = await askToUpdate(result);
1036
+ if (choice === "skip") {
1037
+ skipVersion(result.latestVersion);
1038
+ p4.log.info(`Skipped version ${result.latestVersion}`);
1039
+ return;
1040
+ }
1041
+ if (choice === "later") {
1042
+ p4.log.info("Update postponed");
1043
+ return;
1044
+ }
1045
+ }
1046
+ const updateSpinner = p4.spinner();
1047
+ updateSpinner.start(`Updating to ${result.latestVersion}...`);
1048
+ const updateResult = await performUpdate(pm);
1049
+ if (updateResult.success) {
1050
+ updateSpinner.stop("Update completed");
1051
+ showUpdateSuccess(result.latestVersion);
1052
+ } else {
1053
+ updateSpinner.stop("Update failed");
1054
+ showUpdateError(updateResult.error, pm);
1055
+ process.exit(1);
1056
+ }
1057
+ }
1058
+
791
1059
  // src/cli.ts
792
1060
  var pkg2 = await Promise.resolve().then(() => __toESM(require_package(), 1));
793
1061
  var args = process.argv.slice(2);
@@ -814,10 +1082,19 @@ for (let i = 0;i < args.length; i++) {
814
1082
  }
815
1083
  var hasHelp = flags.has("h") || flags.has("help");
816
1084
  var hasVersion = flags.has("v") || flags.has("version");
1085
+ var hasUpdate = flags.has("update");
817
1086
  if (hasVersion) {
818
1087
  console.log(pkg2.version);
819
1088
  process.exit(0);
820
1089
  }
1090
+ if (hasUpdate) {
1091
+ await runUpdate({
1092
+ force: flags.has("f") || flags.has("force"),
1093
+ verbose: flags.has("verbose"),
1094
+ cliName: "env2op"
1095
+ });
1096
+ process.exit(0);
1097
+ }
821
1098
  if (hasHelp || positional.length === 0) {
822
1099
  showHelp();
823
1100
  process.exit(0);
@@ -837,48 +1114,55 @@ await runConvert({
837
1114
  force: flags.has("f") || flags.has("force"),
838
1115
  verbose: flags.has("verbose")
839
1116
  });
1117
+ try {
1118
+ const updateResult = await checkForUpdate();
1119
+ if (updateResult.updateAvailable && !updateResult.isSkipped) {
1120
+ showUpdateNotification(updateResult, "env2op");
1121
+ }
1122
+ } catch {}
840
1123
  function showHelp() {
841
- const name = pc3.bold(pc3.cyan("env2op"));
842
- const version = pc3.dim(`v${pkg2.version}`);
1124
+ const name = pc5.bold(pc5.cyan("env2op"));
1125
+ const version = pc5.dim(`v${pkg2.version}`);
843
1126
  console.log(`
844
1127
  ${name} ${version}
845
1128
  ${pkg2.description}
846
1129
 
847
- ${pc3.bold("USAGE")}
848
- ${pc3.cyan("$")} env2op ${pc3.yellow("<env_file>")} ${pc3.yellow("<vault>")} ${pc3.yellow("<item_name>")} ${pc3.dim("[options]")}
1130
+ ${pc5.bold("USAGE")}
1131
+ ${pc5.cyan("$")} env2op ${pc5.yellow("<env_file>")} ${pc5.yellow("<vault>")} ${pc5.yellow("<item_name>")} ${pc5.dim("[options]")}
849
1132
 
850
- ${pc3.bold("ARGUMENTS")}
851
- ${pc3.yellow("env_file")} Path to .env file
852
- ${pc3.yellow("vault")} 1Password vault name
853
- ${pc3.yellow("item_name")} Name for the Secure Note in 1Password
1133
+ ${pc5.bold("ARGUMENTS")}
1134
+ ${pc5.yellow("env_file")} Path to .env file
1135
+ ${pc5.yellow("vault")} 1Password vault name
1136
+ ${pc5.yellow("item_name")} Name for the Secure Note in 1Password
854
1137
 
855
- ${pc3.bold("OPTIONS")}
856
- ${pc3.cyan("-o, --output")} Output template path (default: <env_file>.tpl)
857
- ${pc3.cyan("-f, --force")} Skip confirmation prompts
858
- ${pc3.cyan("--dry-run")} Preview actions without executing
859
- ${pc3.cyan("--secret")} Store all fields as password type (hidden)
860
- ${pc3.cyan("--verbose")} Show op CLI output
861
- ${pc3.cyan("-h, --help")} Show this help message
862
- ${pc3.cyan("-v, --version")} Show version
1138
+ ${pc5.bold("OPTIONS")}
1139
+ ${pc5.cyan("-o, --output")} Output template path (default: <env_file>.tpl)
1140
+ ${pc5.cyan("-f, --force")} Skip confirmation prompts
1141
+ ${pc5.cyan(" --dry-run")} Preview actions without executing
1142
+ ${pc5.cyan(" --secret")} Store all fields as password type (hidden)
1143
+ ${pc5.cyan(" --verbose")} Show op CLI output
1144
+ ${pc5.cyan(" --update")} Check for and install updates
1145
+ ${pc5.cyan("-v, --version")} Show version
1146
+ ${pc5.cyan("-h, --help")} Show this help message
863
1147
 
864
- ${pc3.bold("EXAMPLES")}
865
- ${pc3.dim("# Basic usage")}
866
- ${pc3.cyan("$")} env2op .env.production Personal "MyApp - Production"
1148
+ ${pc5.bold("EXAMPLES")}
1149
+ ${pc5.dim("# Basic usage")}
1150
+ ${pc5.cyan("$")} env2op .env.production Personal "MyApp - Production"
867
1151
 
868
- ${pc3.dim("# Custom output path")}
869
- ${pc3.cyan("$")} env2op .env Personal "MyApp" -o secrets.tpl
1152
+ ${pc5.dim("# Custom output path")}
1153
+ ${pc5.cyan("$")} env2op .env Personal "MyApp" -o secrets.tpl
870
1154
 
871
- ${pc3.dim("# Preview without making changes")}
872
- ${pc3.cyan("$")} env2op .env Personal "MyApp" --dry-run
1155
+ ${pc5.dim("# Preview without making changes")}
1156
+ ${pc5.cyan("$")} env2op .env Personal "MyApp" --dry-run
873
1157
 
874
- ${pc3.dim("# Store as hidden password fields")}
875
- ${pc3.cyan("$")} env2op .env Personal "MyApp" --secret
1158
+ ${pc5.dim("# Store as hidden password fields")}
1159
+ ${pc5.cyan("$")} env2op .env Personal "MyApp" --secret
876
1160
 
877
- ${pc3.dim("# Skip confirmation prompts (for CI/scripts)")}
878
- ${pc3.cyan("$")} env2op .env Personal "MyApp" -f
1161
+ ${pc5.dim("# Skip confirmation prompts (for CI/scripts)")}
1162
+ ${pc5.cyan("$")} env2op .env Personal "MyApp" -f
879
1163
 
880
- ${pc3.bold("DOCUMENTATION")}
881
- ${pc3.dim("https://github.com/tolgamorf/env2op-cli")}
1164
+ ${pc5.bold("DOCUMENTATION")}
1165
+ ${pc5.dim("https://github.com/tolgamorf/env2op-cli")}
882
1166
  `);
883
1167
  }
884
1168
  function showMissingArgsError(provided) {
@@ -890,17 +1174,17 @@ function showMissingArgsError(provided) {
890
1174
  if (provided.length < 3)
891
1175
  missing.push("item_name");
892
1176
  console.log(`
893
- ${pc3.red(pc3.bold("Error:"))} Missing required arguments
1177
+ ${pc5.red(pc5.bold("Error:"))} Missing required arguments
894
1178
 
895
- ${pc3.bold("Usage:")} env2op ${pc3.yellow("<env_file>")} ${pc3.yellow("<vault>")} ${pc3.yellow("<item_name>")} ${pc3.dim("[options]")}
1179
+ ${pc5.bold("Usage:")} env2op ${pc5.yellow("<env_file>")} ${pc5.yellow("<vault>")} ${pc5.yellow("<item_name>")} ${pc5.dim("[options]")}
896
1180
 
897
- ${pc3.bold("Missing:")}
898
- ${missing.map((arg) => ` ${pc3.red("•")} ${pc3.yellow(arg)}`).join(`
1181
+ ${pc5.bold("Missing:")}
1182
+ ${missing.map((arg) => ` ${pc5.red("•")} ${pc5.yellow(arg)}`).join(`
899
1183
  `)}
900
1184
 
901
- ${pc3.bold("Example:")}
902
- ${pc3.cyan("$")} env2op .env.production Personal "MyApp - Production"
1185
+ ${pc5.bold("Example:")}
1186
+ ${pc5.cyan("$")} env2op .env.production Personal "MyApp - Production"
903
1187
 
904
- Run ${pc3.cyan("env2op --help")} for more information.
1188
+ Run ${pc5.cyan("env2op --help")} for more information.
905
1189
  `);
906
1190
  }
package/dist/index.js CHANGED
@@ -361,7 +361,7 @@ import { writeFileSync } from "node:fs";
361
361
  // package.json
362
362
  var package_default = {
363
363
  name: "@tolgamorf/env2op-cli",
364
- version: "0.2.1",
364
+ version: "0.2.2",
365
365
  description: "Convert .env files to 1Password Secure Notes and generate templates for op inject/run",
366
366
  type: "module",
367
367
  main: "dist/index.js",
@@ -22,7 +22,7 @@ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports,
22
22
  var require_package = __commonJS((exports, module) => {
23
23
  module.exports = {
24
24
  name: "@tolgamorf/env2op-cli",
25
- version: "0.2.1",
25
+ version: "0.2.2",
26
26
  description: "Convert .env files to 1Password Secure Notes and generate templates for op inject/run",
27
27
  type: "module",
28
28
  main: "dist/index.js",
@@ -102,7 +102,7 @@ var require_package = __commonJS((exports, module) => {
102
102
  });
103
103
 
104
104
  // src/op2env-cli.ts
105
- import pc3 from "picocolors";
105
+ import pc5 from "picocolors";
106
106
 
107
107
  // src/commands/inject.ts
108
108
  import { existsSync, readFileSync, writeFileSync as writeFileSync2 } from "node:fs";
@@ -733,6 +733,274 @@ async function runInject(options) {
733
733
  }
734
734
  }
735
735
 
736
+ // src/commands/update.ts
737
+ import * as p4 from "@clack/prompts";
738
+ import pc4 from "picocolors";
739
+
740
+ // src/lib/package-manager.ts
741
+ var UPDATE_COMMANDS = {
742
+ homebrew: "brew upgrade tolgamorf/tap/env2op-cli",
743
+ npm: "npm update -g @tolgamorf/env2op-cli",
744
+ bun: "bun update -g @tolgamorf/env2op-cli",
745
+ pnpm: "pnpm update -g @tolgamorf/env2op-cli",
746
+ unknown: "npm update -g @tolgamorf/env2op-cli"
747
+ };
748
+ var DISPLAY_NAMES = {
749
+ homebrew: "Homebrew",
750
+ npm: "npm",
751
+ bun: "Bun",
752
+ pnpm: "pnpm",
753
+ unknown: "npm (default)"
754
+ };
755
+ function detectFromPath() {
756
+ const binPath = process.argv[1] ?? "";
757
+ if (binPath.includes("/Cellar/") || binPath.includes("/homebrew/") || binPath.includes("/opt/homebrew/") || binPath.includes("/home/linuxbrew/")) {
758
+ return "homebrew";
759
+ }
760
+ if (binPath.includes("/.bun/")) {
761
+ return "bun";
762
+ }
763
+ if (binPath.includes("/pnpm/") || binPath.includes("/.pnpm/")) {
764
+ return "pnpm";
765
+ }
766
+ if (binPath.includes("/node_modules/")) {
767
+ return "npm";
768
+ }
769
+ return null;
770
+ }
771
+ async function detectFromCommands() {
772
+ const brewResult = await exec("brew", ["list", "env2op-cli"], { verbose: false });
773
+ if (brewResult.exitCode === 0) {
774
+ return "homebrew";
775
+ }
776
+ return "npm";
777
+ }
778
+ async function detectPackageManager() {
779
+ const fromPath = detectFromPath();
780
+ if (fromPath) {
781
+ return {
782
+ type: fromPath,
783
+ updateCommand: UPDATE_COMMANDS[fromPath],
784
+ displayName: DISPLAY_NAMES[fromPath]
785
+ };
786
+ }
787
+ const fromCommands = await detectFromCommands();
788
+ return {
789
+ type: fromCommands,
790
+ updateCommand: UPDATE_COMMANDS[fromCommands],
791
+ displayName: DISPLAY_NAMES[fromCommands]
792
+ };
793
+ }
794
+
795
+ // src/lib/update.ts
796
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
797
+ import { homedir } from "node:os";
798
+ import { join } from "node:path";
799
+ var CACHE_DIR = join(homedir(), ".env2op");
800
+ var CACHE_FILE = join(CACHE_DIR, "update-check.json");
801
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
802
+ function getCliVersion() {
803
+ try {
804
+ const pkg2 = require_package();
805
+ return pkg2.version ?? "0.0.0";
806
+ } catch {
807
+ return "0.0.0";
808
+ }
809
+ }
810
+ function loadCache() {
811
+ try {
812
+ if (existsSync2(CACHE_FILE)) {
813
+ const content = readFileSync2(CACHE_FILE, "utf-8");
814
+ return JSON.parse(content);
815
+ }
816
+ } catch {}
817
+ return { lastCheck: 0, latestVersion: null };
818
+ }
819
+ function saveCache(cache) {
820
+ try {
821
+ if (!existsSync2(CACHE_DIR)) {
822
+ mkdirSync(CACHE_DIR, { recursive: true });
823
+ }
824
+ writeFileSync3(CACHE_FILE, JSON.stringify(cache, null, 2));
825
+ } catch {}
826
+ }
827
+ function shouldCheckForUpdate(cache) {
828
+ const now = Date.now();
829
+ return now - cache.lastCheck > CHECK_INTERVAL_MS;
830
+ }
831
+ async function fetchLatestVersion() {
832
+ try {
833
+ const response = await fetch("https://registry.npmjs.org/@tolgamorf/env2op-cli/latest");
834
+ if (!response.ok)
835
+ return null;
836
+ const data = await response.json();
837
+ return data.version ?? null;
838
+ } catch {
839
+ return null;
840
+ }
841
+ }
842
+ function compareVersions(v1, v2) {
843
+ const parts1 = v1.split(".").map(Number);
844
+ const parts2 = v2.split(".").map(Number);
845
+ for (let i = 0;i < 3; i++) {
846
+ const p1 = parts1[i] || 0;
847
+ const p22 = parts2[i] || 0;
848
+ if (p1 < p22)
849
+ return -1;
850
+ if (p1 > p22)
851
+ return 1;
852
+ }
853
+ return 0;
854
+ }
855
+ async function checkForUpdate(forceCheck = false) {
856
+ const currentVersion = getCliVersion();
857
+ const cache = loadCache();
858
+ if (!forceCheck && !shouldCheckForUpdate(cache) && cache.latestVersion) {
859
+ const updateAvailable2 = compareVersions(currentVersion, cache.latestVersion) < 0;
860
+ const isSkipped2 = cache.skipVersion === cache.latestVersion;
861
+ return {
862
+ currentVersion,
863
+ latestVersion: cache.latestVersion,
864
+ updateAvailable: updateAvailable2,
865
+ isSkipped: isSkipped2,
866
+ fromCache: true
867
+ };
868
+ }
869
+ const latestVersion = await fetchLatestVersion();
870
+ saveCache({
871
+ ...cache,
872
+ lastCheck: Date.now(),
873
+ latestVersion
874
+ });
875
+ if (!latestVersion) {
876
+ return {
877
+ currentVersion,
878
+ latestVersion: null,
879
+ updateAvailable: false,
880
+ isSkipped: false,
881
+ fromCache: false
882
+ };
883
+ }
884
+ const updateAvailable = compareVersions(currentVersion, latestVersion) < 0;
885
+ const isSkipped = cache.skipVersion === latestVersion;
886
+ return {
887
+ currentVersion,
888
+ latestVersion,
889
+ updateAvailable,
890
+ isSkipped,
891
+ fromCache: false
892
+ };
893
+ }
894
+ async function performUpdate(pm) {
895
+ const packageManager = pm ?? await detectPackageManager();
896
+ try {
897
+ const [command, ...args] = packageManager.updateCommand.split(" ");
898
+ const result = await exec(command, args, { verbose: false });
899
+ if (result.exitCode !== 0) {
900
+ return {
901
+ success: false,
902
+ error: result.stderr || `Command exited with code ${result.exitCode}`
903
+ };
904
+ }
905
+ return { success: true };
906
+ } catch (error) {
907
+ const message = error instanceof Error ? error.message : String(error);
908
+ return { success: false, error: message };
909
+ }
910
+ }
911
+ function skipVersion(version) {
912
+ const cache = loadCache();
913
+ cache.skipVersion = version;
914
+ saveCache(cache);
915
+ }
916
+
917
+ // src/lib/update-prompts.ts
918
+ import * as p3 from "@clack/prompts";
919
+ import pc3 from "picocolors";
920
+ var S_BAR_START = "┌";
921
+ var S_BAR_END = "└";
922
+ function showUpdateNotification(result, cliName = "env2op") {
923
+ console.log();
924
+ console.log(`${pc3.gray(S_BAR_START)}${pc3.gray("─")} ${pc3.yellow("Update available:")} ${pc3.dim(result.currentVersion)} ${pc3.dim("→")} ${pc3.green(result.latestVersion)}`);
925
+ console.log(`${pc3.gray(S_BAR_END)}${pc3.gray("─")} Run ${pc3.cyan(`'${cliName} update'`)} to update`);
926
+ }
927
+ async function askToUpdate(result) {
928
+ const response = await p3.select({
929
+ message: "Would you like to update?",
930
+ options: [
931
+ { value: "update", label: "Update now", hint: "Download and install the latest version" },
932
+ { value: "later", label: "Remind me later", hint: "Ask again next time" },
933
+ { value: "skip", label: "Skip this version", hint: `Don't ask about ${result.latestVersion} again` }
934
+ ]
935
+ });
936
+ if (p3.isCancel(response)) {
937
+ return "later";
938
+ }
939
+ return response;
940
+ }
941
+ function showUpdateAvailable(result) {
942
+ p3.log.success(`Update available: ${pc3.dim(result.currentVersion)} ${pc3.dim("→")} ${pc3.green(result.latestVersion)}`);
943
+ }
944
+ function showUpToDate(currentVersion) {
945
+ p3.log.success(`You're on the latest version ${pc3.green(`(${currentVersion})`)}`);
946
+ }
947
+ function showPackageManagerInfo(pm) {
948
+ console.log();
949
+ console.log(` ${pc3.dim("Detected:")} ${pm.displayName} installation`);
950
+ console.log(` ${pc3.dim("Command:")} ${pc3.cyan(pm.updateCommand)}`);
951
+ console.log();
952
+ }
953
+ function showUpdateSuccess(newVersion) {
954
+ p3.log.success(`Updated to version ${pc3.green(newVersion)}`);
955
+ p3.log.info("Please restart to use the new version.");
956
+ }
957
+ function showUpdateError(error, pm) {
958
+ if (error) {
959
+ p3.log.error(pc3.dim(error));
960
+ }
961
+ p3.log.info(`Try running manually: ${pc3.cyan(pm.updateCommand)}`);
962
+ }
963
+
964
+ // src/commands/update.ts
965
+ async function runUpdate(options) {
966
+ const { force = false, cliName = "env2op" } = options;
967
+ p4.intro(pc4.bgCyan(pc4.black(` ${cliName} update `)));
968
+ const spinner4 = p4.spinner();
969
+ spinner4.start("Checking for updates...");
970
+ const result = await checkForUpdate(true);
971
+ spinner4.stop("Checked for updates");
972
+ if (!result.updateAvailable || !result.latestVersion) {
973
+ showUpToDate(result.currentVersion);
974
+ return;
975
+ }
976
+ showUpdateAvailable(result);
977
+ const pm = await detectPackageManager();
978
+ showPackageManagerInfo(pm);
979
+ if (!force) {
980
+ const choice = await askToUpdate(result);
981
+ if (choice === "skip") {
982
+ skipVersion(result.latestVersion);
983
+ p4.log.info(`Skipped version ${result.latestVersion}`);
984
+ return;
985
+ }
986
+ if (choice === "later") {
987
+ p4.log.info("Update postponed");
988
+ return;
989
+ }
990
+ }
991
+ const updateSpinner = p4.spinner();
992
+ updateSpinner.start(`Updating to ${result.latestVersion}...`);
993
+ const updateResult = await performUpdate(pm);
994
+ if (updateResult.success) {
995
+ updateSpinner.stop("Update completed");
996
+ showUpdateSuccess(result.latestVersion);
997
+ } else {
998
+ updateSpinner.stop("Update failed");
999
+ showUpdateError(updateResult.error, pm);
1000
+ process.exit(1);
1001
+ }
1002
+ }
1003
+
736
1004
  // src/op2env-cli.ts
737
1005
  var pkg2 = await Promise.resolve().then(() => __toESM(require_package(), 1));
738
1006
  var args = process.argv.slice(2);
@@ -759,10 +1027,19 @@ for (let i = 0;i < args.length; i++) {
759
1027
  }
760
1028
  var hasHelp = flags.has("h") || flags.has("help");
761
1029
  var hasVersion = flags.has("v") || flags.has("version");
1030
+ var hasUpdate = flags.has("update");
762
1031
  if (hasVersion) {
763
1032
  console.log(pkg2.version);
764
1033
  process.exit(0);
765
1034
  }
1035
+ if (hasUpdate) {
1036
+ await runUpdate({
1037
+ force: flags.has("f") || flags.has("force"),
1038
+ verbose: flags.has("verbose"),
1039
+ cliName: "op2env"
1040
+ });
1041
+ process.exit(0);
1042
+ }
766
1043
  if (hasHelp || positional.length === 0) {
767
1044
  showHelp();
768
1045
  process.exit(0);
@@ -775,41 +1052,48 @@ await runInject({
775
1052
  force: flags.has("f") || flags.has("force"),
776
1053
  verbose: flags.has("verbose")
777
1054
  });
1055
+ try {
1056
+ const updateResult = await checkForUpdate();
1057
+ if (updateResult.updateAvailable && !updateResult.isSkipped) {
1058
+ showUpdateNotification(updateResult, "op2env");
1059
+ }
1060
+ } catch {}
778
1061
  function showHelp() {
779
- const name = pc3.bold(pc3.cyan("op2env"));
780
- const version = pc3.dim(`v${pkg2.version}`);
1062
+ const name = pc5.bold(pc5.cyan("op2env"));
1063
+ const version = pc5.dim(`v${pkg2.version}`);
781
1064
  console.log(`
782
1065
  ${name} ${version}
783
1066
  Pull secrets from 1Password to generate .env files
784
1067
 
785
- ${pc3.bold("USAGE")}
786
- ${pc3.cyan("$")} op2env ${pc3.yellow("<template_file>")} ${pc3.dim("[options]")}
1068
+ ${pc5.bold("USAGE")}
1069
+ ${pc5.cyan("$")} op2env ${pc5.yellow("<template_file>")} ${pc5.dim("[options]")}
787
1070
 
788
- ${pc3.bold("ARGUMENTS")}
789
- ${pc3.yellow("template_file")} Path to .env.tpl template file
1071
+ ${pc5.bold("ARGUMENTS")}
1072
+ ${pc5.yellow("template_file")} Path to .env.tpl template file
790
1073
 
791
- ${pc3.bold("OPTIONS")}
792
- ${pc3.cyan("-o, --output")} Output .env path (default: template without .tpl)
793
- ${pc3.cyan("-f, --force")} Overwrite without prompting
794
- ${pc3.cyan("--dry-run")} Preview actions without executing
795
- ${pc3.cyan("--verbose")} Show op CLI output
796
- ${pc3.cyan("-h, --help")} Show this help message
797
- ${pc3.cyan("-v, --version")} Show version
1074
+ ${pc5.bold("OPTIONS")}
1075
+ ${pc5.cyan("-o, --output")} Output .env path (default: template without .tpl)
1076
+ ${pc5.cyan("-f, --force")} Overwrite without prompting
1077
+ ${pc5.cyan(" --dry-run")} Preview actions without executing
1078
+ ${pc5.cyan(" --verbose")} Show op CLI output
1079
+ ${pc5.cyan(" --update")} Check for and install updates
1080
+ ${pc5.cyan("-v, --version")} Show version
1081
+ ${pc5.cyan("-h, --help")} Show this help message
798
1082
 
799
- ${pc3.bold("EXAMPLES")}
800
- ${pc3.dim("# Basic usage - generates .env from .env.tpl")}
801
- ${pc3.cyan("$")} op2env .env.tpl
1083
+ ${pc5.bold("EXAMPLES")}
1084
+ ${pc5.dim("# Basic usage - generates .env from .env.tpl")}
1085
+ ${pc5.cyan("$")} op2env .env.tpl
802
1086
 
803
- ${pc3.dim("# Custom output path")}
804
- ${pc3.cyan("$")} op2env .env.tpl -o .env.local
1087
+ ${pc5.dim("# Custom output path")}
1088
+ ${pc5.cyan("$")} op2env .env.tpl -o .env.local
805
1089
 
806
- ${pc3.dim("# Preview without making changes")}
807
- ${pc3.cyan("$")} op2env .env.tpl --dry-run
1090
+ ${pc5.dim("# Preview without making changes")}
1091
+ ${pc5.cyan("$")} op2env .env.tpl --dry-run
808
1092
 
809
- ${pc3.dim("# Overwrite existing .env without prompting")}
810
- ${pc3.cyan("$")} op2env .env.tpl -f
1093
+ ${pc5.dim("# Overwrite existing .env without prompting")}
1094
+ ${pc5.cyan("$")} op2env .env.tpl -f
811
1095
 
812
- ${pc3.bold("DOCUMENTATION")}
813
- ${pc3.dim("https://github.com/tolgamorf/env2op-cli")}
1096
+ ${pc5.bold("DOCUMENTATION")}
1097
+ ${pc5.dim("https://github.com/tolgamorf/env2op-cli")}
814
1098
  `);
815
1099
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tolgamorf/env2op-cli",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Convert .env files to 1Password Secure Notes and generate templates for op inject/run",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",