envlock-next 0.4.0 → 0.6.0

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/dist/cli/index.js CHANGED
@@ -2,29 +2,162 @@
2
2
 
3
3
  // src/cli/index.ts
4
4
  import { pathToFileURL as pathToFileURL2 } from "url";
5
+ import { realpathSync } from "fs";
5
6
  import { Command } from "commander";
6
- import { ENVIRONMENTS, runWithSecrets, validateEnvFilePath, validateOnePasswordEnvId as validateOnePasswordEnvId2 } from "envlock-core";
7
+
8
+ // ../core/dist/index.js
9
+ import { spawnSync } from "child_process";
10
+ import { execFileSync } from "child_process";
11
+ import { isAbsolute, relative, resolve } from "path";
12
+ import { createServer } from "net";
13
+ var verbose = false;
14
+ function setVerbose(flag) {
15
+ verbose = flag;
16
+ }
17
+ var log = {
18
+ debug: (msg) => {
19
+ if (verbose) process.stderr.write(`[envlock:debug] ${msg}
20
+ `);
21
+ },
22
+ info: (msg) => {
23
+ process.stderr.write(`[envlock] ${msg}
24
+ `);
25
+ },
26
+ warn: (msg) => {
27
+ process.stderr.write(`[envlock] Warning: ${msg}
28
+ `);
29
+ },
30
+ error: (msg) => {
31
+ process.stderr.write(`[envlock] Error: ${msg}
32
+ `);
33
+ }
34
+ };
35
+ var WHICH = process.platform === "win32" ? "where" : "which";
36
+ function hasBinary(name) {
37
+ try {
38
+ execFileSync(WHICH, [name], { stdio: "pipe" });
39
+ return true;
40
+ } catch {
41
+ return false;
42
+ }
43
+ }
44
+ function checkBinary(name, installHint) {
45
+ if (!hasBinary(name)) {
46
+ throw new Error(`[envlock] '${name}' not found in PATH.
47
+ ${installHint}`);
48
+ }
49
+ log.debug(`Binary check: ${name} found`);
50
+ }
51
+ function runWithSecrets(options) {
52
+ const { envFile, environment, onePasswordEnvId, command, args } = options;
53
+ checkBinary(
54
+ "dotenvx",
55
+ "Install dotenvx: npm install -g @dotenvx/dotenvx\nOr add it as a dev dependency."
56
+ );
57
+ const privateKeyVar = `DOTENV_PRIVATE_KEY_${environment.toUpperCase()}`;
58
+ const keyAlreadyInjected = !!process.env[privateKeyVar];
59
+ let result;
60
+ if (keyAlreadyInjected) {
61
+ log.debug(`Spawning: dotenvx run -f ${envFile} -- ${command} ${args.join(" ")}`);
62
+ result = spawnSync(
63
+ "dotenvx",
64
+ ["run", "-f", envFile, "--", command, ...args],
65
+ { stdio: "inherit" }
66
+ );
67
+ if (result.error) {
68
+ throw new Error(`[envlock] Failed to spawn 'dotenvx': ${result.error.message}`);
69
+ }
70
+ } else {
71
+ checkBinary(
72
+ "op",
73
+ "Install 1Password CLI: brew install --cask 1password-cli@beta\nThen sign in: op signin"
74
+ );
75
+ log.debug(`Spawning: op run --environment ${onePasswordEnvId} -- dotenvx run -f ${envFile} -- ${command} ${args.join(" ")}`);
76
+ result = spawnSync(
77
+ "op",
78
+ [
79
+ "run",
80
+ "--environment",
81
+ onePasswordEnvId,
82
+ "--",
83
+ "dotenvx",
84
+ "run",
85
+ "-f",
86
+ envFile,
87
+ "--",
88
+ command,
89
+ ...args
90
+ ],
91
+ { stdio: "inherit" }
92
+ );
93
+ if (result.error) {
94
+ throw new Error(`[envlock] Failed to spawn 'op': ${result.error.message}`);
95
+ }
96
+ }
97
+ process.exit(result.status ?? 1);
98
+ }
99
+ var OP_ENV_ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
100
+ function validateOnePasswordEnvId(id) {
101
+ if (!id || !OP_ENV_ID_PATTERN.test(id)) {
102
+ throw new Error(
103
+ `[envlock] Invalid onePasswordEnvId: "${id}". Must be a lowercase alphanumeric string (hyphens allowed), e.g. 'ca6uypwvab5mevel44gqdc2zae'.`
104
+ );
105
+ }
106
+ }
107
+ function validateEnvFilePath(envFile, cwd) {
108
+ if (envFile.includes("\0")) {
109
+ throw new Error(`[envlock] Invalid env file path: null bytes are not allowed.`);
110
+ }
111
+ const resolved = resolve(cwd, envFile);
112
+ const base = resolve(cwd);
113
+ const rel = relative(base, resolved);
114
+ if (rel.startsWith("..") || isAbsolute(rel)) {
115
+ throw new Error(
116
+ `[envlock] Invalid env file path: "${envFile}" resolves outside the project directory.`
117
+ );
118
+ }
119
+ }
120
+ var ENVIRONMENTS = {
121
+ development: "development",
122
+ staging: "staging",
123
+ production: "production"
124
+ };
125
+ function isPortFree(port) {
126
+ return new Promise((resolve22) => {
127
+ const server = createServer();
128
+ server.once("error", () => resolve22(false));
129
+ server.once("listening", () => server.close(() => resolve22(true)));
130
+ server.listen(port, "127.0.0.1");
131
+ });
132
+ }
133
+ async function findFreePort(preferred) {
134
+ for (let port = preferred; port <= preferred + 10; port++) {
135
+ if (await isPortFree(port)) return port;
136
+ }
137
+ throw new Error(
138
+ `[envlock] No free port found in range ${preferred}\u2013${preferred + 10}.`
139
+ );
140
+ }
7
141
 
8
142
  // src/cli/resolve-config.ts
9
143
  import { existsSync } from "fs";
10
- import { resolve } from "path";
144
+ import { resolve as resolve2 } from "path";
11
145
  import { pathToFileURL } from "url";
12
- import { validateOnePasswordEnvId } from "envlock-core";
13
- var CONFIG_CANDIDATES = ["next.config.js", "next.config.mjs"];
146
+ var CONFIG_CANDIDATES = ["next.config.ts", "next.config.js", "next.config.mjs"];
14
147
  async function resolveConfig(cwd) {
15
148
  for (const candidate of CONFIG_CANDIDATES) {
16
- const fullPath = resolve(cwd, candidate);
149
+ const fullPath = resolve2(cwd, candidate);
17
150
  if (!existsSync(fullPath)) continue;
18
151
  try {
19
152
  const mod = await import(pathToFileURL(fullPath).href);
20
153
  const config = mod.default ?? mod;
21
154
  if (config && typeof config === "object" && "__envlock" in config && config.__envlock && typeof config.__envlock === "object" && "onePasswordEnvId" in config.__envlock) {
155
+ log.debug(`Config loaded from ${candidate}`);
22
156
  return config.__envlock;
23
157
  }
24
158
  } catch (err) {
25
- console.warn(
26
- `[envlock] Failed to load ${candidate}: ${err instanceof Error ? err.message : String(err)}`
27
- );
159
+ log.warn(`Failed to load ${candidate}: ${err instanceof Error ? err.message : String(err)}`);
160
+ log.debug(`Stack: ${err instanceof Error ? err.stack ?? "" : ""}`);
28
161
  }
29
162
  }
30
163
  if (process.env["ENVLOCK_OP_ENV_ID"]) {
@@ -61,12 +194,30 @@ async function runNextCommand(subcommand, environment, passthroughArgs) {
61
194
  const config = await resolveConfig(process.cwd());
62
195
  const envFile = config.envFiles?.[environment] ?? DEFAULT_ENV_FILES[environment];
63
196
  validateEnvFilePath(envFile, process.cwd());
197
+ let finalArgs = [...passthroughArgs];
198
+ if (subcommand === "dev") {
199
+ const portFlagIndex = finalArgs.findIndex(
200
+ (a) => a === "--port" || a === "-p"
201
+ );
202
+ const requestedPort = portFlagIndex !== -1 ? parseInt(finalArgs[portFlagIndex + 1] ?? "3000", 10) : 3e3;
203
+ const freePort = await findFreePort(requestedPort);
204
+ if (freePort !== requestedPort) {
205
+ log.warn(`Port ${requestedPort} in use, switching to ${freePort}`);
206
+ }
207
+ if (portFlagIndex !== -1) {
208
+ finalArgs.splice(portFlagIndex, 2);
209
+ }
210
+ finalArgs = ["-p", String(freePort), ...finalArgs];
211
+ }
212
+ log.debug(`Environment: ${environment}`);
213
+ log.debug(`Env file: ${envFile}`);
214
+ log.debug(`Command: next ${subcommand} ${finalArgs.join(" ")}`);
64
215
  runWithSecrets({
65
216
  envFile,
66
217
  environment,
67
218
  onePasswordEnvId: config.onePasswordEnvId,
68
219
  command: "next",
69
- args: [subcommand, ...passthroughArgs]
220
+ args: [subcommand, ...finalArgs]
70
221
  });
71
222
  }
72
223
  function addEnvFlags(cmd) {
@@ -91,9 +242,12 @@ async function handleRunCommand(cmd, cmdArgs, opts) {
91
242
  "[envlock] No onePasswordEnvId found. Set it in envlock.config.js or via ENVLOCK_OP_ENV_ID env var."
92
243
  );
93
244
  }
94
- validateOnePasswordEnvId2(onePasswordEnvId);
245
+ validateOnePasswordEnvId(onePasswordEnvId);
95
246
  const envFile = config.envFiles?.[environment] ?? DEFAULT_ENV_FILES[environment];
96
247
  validateEnvFilePath(envFile, process.cwd());
248
+ log.debug(`Environment: ${environment}`);
249
+ log.debug(`Env file: ${envFile}`);
250
+ log.debug(`Command: ${cmd} ${cmdArgs.join(" ")}`);
97
251
  runWithSecrets({
98
252
  envFile,
99
253
  environment,
@@ -103,7 +257,7 @@ async function handleRunCommand(cmd, cmdArgs, opts) {
103
257
  });
104
258
  }
105
259
  var program = new Command("envlock");
106
- program.name("envlock").description("Run Next.js commands with 1Password + dotenvx secret injection").version("0.3.0").enablePositionalOptions();
260
+ program.name("envlock").description("Run Next.js commands with 1Password + dotenvx secret injection").version("0.3.0").enablePositionalOptions().option("-d, --debug", "enable debug output");
107
261
  for (const [subcommand, defaultEnv] of Object.entries(
108
262
  SUPPORTED_SUBCOMMANDS
109
263
  )) {
@@ -122,15 +276,27 @@ addEnvFlags(runCmd).action(
122
276
  try {
123
277
  await handleRunCommand(cmd, cmdArgs, opts);
124
278
  } catch (err) {
125
- console.error(err instanceof Error ? err.message : String(err));
279
+ log.error(err instanceof Error ? err.message : String(err));
126
280
  process.exit(1);
127
281
  }
128
282
  }
129
283
  );
130
284
  program.addCommand(runCmd);
131
- if (import.meta.url === pathToFileURL2(process.argv[1] ?? "").href) {
285
+ var _resolvedArgv1Next = (() => {
286
+ try {
287
+ return realpathSync(process.argv[1] ?? "");
288
+ } catch {
289
+ return process.argv[1] ?? "";
290
+ }
291
+ })();
292
+ if (import.meta.url === pathToFileURL2(_resolvedArgv1Next).href) {
293
+ if (process.argv.includes("--debug") || process.argv.includes("-d")) {
294
+ setVerbose(true);
295
+ }
296
+ log.debug(`Resolved argv[1]: ${_resolvedArgv1Next}`);
132
297
  program.parse(process.argv);
133
298
  }
134
299
  export {
135
- handleRunCommand
300
+ handleRunCommand,
301
+ runNextCommand
136
302
  };
package/dist/index.cjs ADDED
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ withEnvlock: () => withEnvlock
24
+ });
25
+ module.exports = __toCommonJS(src_exports);
26
+
27
+ // src/plugin.ts
28
+ var import_envlock_core = require("envlock-core");
29
+ function withEnvlock(nextConfig, options) {
30
+ if (!options?.onePasswordEnvId) {
31
+ console.warn(
32
+ "[envlock] No onePasswordEnvId provided to withEnvlock(). Set it to your 1Password Environment ID for automatic secret injection. Alternatively, set ENVLOCK_OP_ENV_ID in your environment."
33
+ );
34
+ } else {
35
+ (0, import_envlock_core.validateOnePasswordEnvId)(options.onePasswordEnvId);
36
+ }
37
+ return {
38
+ ...nextConfig,
39
+ __envlock: options ?? { onePasswordEnvId: "" }
40
+ };
41
+ }
42
+ // Annotate the CommonJS export names for ESM import in node:
43
+ 0 && (module.exports = {
44
+ withEnvlock
45
+ });
@@ -0,0 +1,10 @@
1
+ import { NextConfig } from 'next';
2
+ import { EnvlockOptions } from 'envlock-core';
3
+ export { EnvlockOptions } from 'envlock-core';
4
+
5
+ type EnvlockNextConfig = NextConfig & {
6
+ __envlock: EnvlockOptions;
7
+ };
8
+ declare function withEnvlock(nextConfig: NextConfig, options?: EnvlockOptions): EnvlockNextConfig;
9
+
10
+ export { type EnvlockNextConfig, withEnvlock };
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/postinstall.ts
21
+ var postinstall_exports = {};
22
+ __export(postinstall_exports, {
23
+ rewriteScripts: () => rewriteScripts
24
+ });
25
+ module.exports = __toCommonJS(postinstall_exports);
26
+ var import_node_fs = require("fs");
27
+ var import_node_path = require("path");
28
+ var REWRITES = [
29
+ { from: /(?<![envlock\s])next dev/, to: "envlock dev", cmd: "dev" },
30
+ { from: /(?<![envlock\s])next build/, to: "envlock build", cmd: "build" },
31
+ { from: /(?<![envlock\s])next start/, to: "envlock start", cmd: "start" }
32
+ ];
33
+ function rewriteScripts(projectRoot2) {
34
+ const pkgPath = (0, import_node_path.join)(projectRoot2, "package.json");
35
+ if (!(0, import_node_fs.existsSync)(pkgPath)) return;
36
+ let pkg;
37
+ try {
38
+ pkg = JSON.parse((0, import_node_fs.readFileSync)(pkgPath, "utf8"));
39
+ } catch {
40
+ return;
41
+ }
42
+ if (!pkg.scripts) return;
43
+ let changed = false;
44
+ for (const { from, to, cmd } of REWRITES) {
45
+ const script = pkg.scripts[cmd];
46
+ if (!script) continue;
47
+ if (script.includes("envlock")) continue;
48
+ if (!from.test(script)) continue;
49
+ pkg.scripts[cmd] = script.replace(from, to);
50
+ console.log(`[envlock] Updated scripts.${cmd}: "${script}" \u2192 "${pkg.scripts[cmd]}"`);
51
+ changed = true;
52
+ }
53
+ if (changed) {
54
+ (0, import_node_fs.writeFileSync)(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
55
+ }
56
+ }
57
+ var projectRoot = process.env["INIT_CWD"];
58
+ if (projectRoot) {
59
+ rewriteScripts(projectRoot);
60
+ }
61
+ // Annotate the CommonJS export names for ESM import in node:
62
+ 0 && (module.exports = {
63
+ rewriteScripts
64
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "envlock-next",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "description": "Next.js plugin and CLI for envlock",
6
6
  "license": "MIT",
@@ -22,24 +22,33 @@
22
22
  },
23
23
  "exports": {
24
24
  ".": {
25
- "import": "./dist/index.js",
26
- "types": "./dist/index.d.ts"
25
+ "types": "./dist/index.d.ts",
26
+ "require": "./dist/index.cjs",
27
+ "import": "./dist/index.js"
27
28
  }
28
29
  },
29
- "files": ["dist"],
30
+ "main": "./dist/index.cjs",
31
+ "files": [
32
+ "dist"
33
+ ],
30
34
  "scripts": {
31
35
  "build": "tsup",
32
36
  "dev": "tsup --watch",
33
37
  "test": "vitest run",
34
- "test:watch": "vitest"
38
+ "test:watch": "vitest",
39
+ "postinstall": "node ./dist/postinstall.cjs"
35
40
  },
36
41
  "dependencies": {
37
- "envlock-core": "workspace:*",
42
+ "envlock-core": "^0.6.0",
38
43
  "commander": "^12.0.0"
39
44
  },
40
45
  "peerDependencies": {
46
+ "@dotenvx/dotenvx": ">=1.0.0",
41
47
  "next": ">=14.0.0"
42
48
  },
49
+ "peerDependenciesMeta": {
50
+ "@dotenvx/dotenvx": { "optional": false }
51
+ },
43
52
  "devDependencies": {
44
53
  "envlock-core": "workspace:*",
45
54
  "@types/node": "^20.14.10",