react-native-config-ultimate 0.1.0 → 0.2.1

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.
Files changed (112) hide show
  1. package/README.md +476 -121
  2. package/android/build.gradle +1 -1
  3. package/android/rncu.gradle +19 -15
  4. package/android/src/main/java/com/reactnativeultimateconfig/UltimateConfigHelper.java +87 -0
  5. package/android/src/main/java/com/reactnativeultimateconfig/UltimateConfigPackage.java +1 -1
  6. package/android/src/newarch/java/com/reactnativeultimateconfig/UltimateConfigModule.java +3 -50
  7. package/android/src/oldarch/java/com/reactnativeultimateconfig/UltimateConfigModule.java +3 -54
  8. package/ios/UltimateConfig.mm +2 -1
  9. package/lib/commonjs/cli.js +11 -2
  10. package/lib/commonjs/cli.js.map +1 -1
  11. package/lib/commonjs/flatten.js +6 -1
  12. package/lib/commonjs/flatten.js.map +1 -1
  13. package/lib/commonjs/load-env.js +14 -0
  14. package/lib/commonjs/load-env.js.map +1 -1
  15. package/lib/commonjs/main.js +5 -1
  16. package/lib/commonjs/main.js.map +1 -1
  17. package/lib/commonjs/render-env.js +1 -1
  18. package/lib/commonjs/render-env.js.map +1 -1
  19. package/lib/commonjs/resolve-env.js +5 -1
  20. package/lib/commonjs/resolve-env.js.map +1 -1
  21. package/lib/commonjs/templates/override.js.handlebars +1 -10
  22. package/lib/commonjs/validate-env.js +20 -7
  23. package/lib/commonjs/validate-env.js.map +1 -1
  24. package/lib/module/cli.js +11 -2
  25. package/lib/module/cli.js.map +1 -1
  26. package/lib/module/flatten.js +6 -1
  27. package/lib/module/flatten.js.map +1 -1
  28. package/lib/module/load-env.js +14 -0
  29. package/lib/module/load-env.js.map +1 -1
  30. package/lib/module/main.js +6 -2
  31. package/lib/module/main.js.map +1 -1
  32. package/lib/module/render-env.js +1 -1
  33. package/lib/module/render-env.js.map +1 -1
  34. package/lib/module/resolve-env.js +5 -1
  35. package/lib/module/resolve-env.js.map +1 -1
  36. package/lib/module/templates/override.js.handlebars +1 -10
  37. package/lib/module/validate-env.js +19 -7
  38. package/lib/module/validate-env.js.map +1 -1
  39. package/lib/typescript/src/cli.d.ts.map +1 -1
  40. package/lib/typescript/src/flatten.d.ts.map +1 -1
  41. package/lib/typescript/src/load-env.d.ts.map +1 -1
  42. package/lib/typescript/src/main.d.ts +1 -1
  43. package/lib/typescript/src/main.d.ts.map +1 -1
  44. package/lib/typescript/src/render-env.d.ts.map +1 -1
  45. package/lib/typescript/src/resolve-env.d.ts +8 -1
  46. package/lib/typescript/src/resolve-env.d.ts.map +1 -1
  47. package/lib/typescript/src/validate-env.d.ts +8 -0
  48. package/lib/typescript/src/validate-env.d.ts.map +1 -1
  49. package/package.json +4 -2
  50. package/src/cli.ts +12 -2
  51. package/src/flatten.ts +6 -1
  52. package/src/load-env.ts +18 -0
  53. package/src/main.ts +6 -2
  54. package/src/render-env.ts +4 -1
  55. package/src/resolve-env.ts +16 -2
  56. package/src/templates/override.js.handlebars +1 -10
  57. package/src/validate-env.ts +23 -7
  58. package/android/rnuc.yaml +0 -1
  59. package/lib/commonjs/bin.spec.js +0 -50
  60. package/lib/commonjs/bin.spec.js.map +0 -1
  61. package/lib/commonjs/cli.spec.js +0 -190
  62. package/lib/commonjs/cli.spec.js.map +0 -1
  63. package/lib/commonjs/flatten.spec.js +0 -32
  64. package/lib/commonjs/flatten.spec.js.map +0 -1
  65. package/lib/commonjs/load-env.spec.js +0 -257
  66. package/lib/commonjs/load-env.spec.js.map +0 -1
  67. package/lib/commonjs/main.spec.js +0 -228
  68. package/lib/commonjs/main.spec.js.map +0 -1
  69. package/lib/commonjs/render-env.spec.js +0 -397
  70. package/lib/commonjs/render-env.spec.js.map +0 -1
  71. package/lib/commonjs/resolve-env.spec.js +0 -31
  72. package/lib/commonjs/resolve-env.spec.js.map +0 -1
  73. package/lib/commonjs/validate-env.spec.js +0 -325
  74. package/lib/commonjs/validate-env.spec.js.map +0 -1
  75. package/lib/commonjs/write-env.spec.js +0 -115
  76. package/lib/commonjs/write-env.spec.js.map +0 -1
  77. package/lib/module/bin.spec.js +0 -49
  78. package/lib/module/bin.spec.js.map +0 -1
  79. package/lib/module/cli.spec.js +0 -190
  80. package/lib/module/cli.spec.js.map +0 -1
  81. package/lib/module/flatten.spec.js +0 -31
  82. package/lib/module/flatten.spec.js.map +0 -1
  83. package/lib/module/load-env.spec.js +0 -257
  84. package/lib/module/load-env.spec.js.map +0 -1
  85. package/lib/module/main.spec.js +0 -224
  86. package/lib/module/main.spec.js.map +0 -1
  87. package/lib/module/render-env.spec.js +0 -396
  88. package/lib/module/render-env.spec.js.map +0 -1
  89. package/lib/module/resolve-env.spec.js +0 -30
  90. package/lib/module/resolve-env.spec.js.map +0 -1
  91. package/lib/module/validate-env.spec.js +0 -325
  92. package/lib/module/validate-env.spec.js.map +0 -1
  93. package/lib/module/write-env.spec.js +0 -115
  94. package/lib/module/write-env.spec.js.map +0 -1
  95. package/lib/typescript/src/bin.spec.d.ts +0 -2
  96. package/lib/typescript/src/bin.spec.d.ts.map +0 -1
  97. package/lib/typescript/src/cli.spec.d.ts +0 -14
  98. package/lib/typescript/src/cli.spec.d.ts.map +0 -1
  99. package/lib/typescript/src/flatten.spec.d.ts +0 -2
  100. package/lib/typescript/src/flatten.spec.d.ts.map +0 -1
  101. package/lib/typescript/src/load-env.spec.d.ts +0 -6
  102. package/lib/typescript/src/load-env.spec.d.ts.map +0 -1
  103. package/lib/typescript/src/main.spec.d.ts +0 -2
  104. package/lib/typescript/src/main.spec.d.ts.map +0 -1
  105. package/lib/typescript/src/render-env.spec.d.ts +0 -2
  106. package/lib/typescript/src/render-env.spec.d.ts.map +0 -1
  107. package/lib/typescript/src/resolve-env.spec.d.ts +0 -2
  108. package/lib/typescript/src/resolve-env.spec.d.ts.map +0 -1
  109. package/lib/typescript/src/validate-env.spec.d.ts +0 -2
  110. package/lib/typescript/src/validate-env.spec.d.ts.map +0 -1
  111. package/lib/typescript/src/write-env.spec.d.ts +0 -9
  112. package/lib/typescript/src/write-env.spec.d.ts.map +0 -1
@@ -26,7 +26,14 @@ export interface SchemaField {
26
26
  export type Schema = Record<string, SchemaField>;
27
27
 
28
28
  export interface RC {
29
- on_env?: (env: EnvData) => unknown;
29
+ /**
30
+ * Hook to transform env data before validation and rendering.
31
+ * Return the modified env object, or `undefined`/`void` to keep the original.
32
+ *
33
+ * **Important:** if you mutate `env` in place without returning it, the
34
+ * mutations are ignored. Always return the modified object.
35
+ */
36
+ on_env?: (env: EnvData) => EnvData | undefined | void | Promise<EnvData | undefined | void>;
30
37
  js_override?: boolean;
31
38
  /**
32
39
  * Optional schema for build-time validation of env vars.
@@ -38,7 +45,14 @@ export interface RC {
38
45
  export default async function resolve_env(env: EnvData, rc?: RC): Promise<EnvData> {
39
46
  if (rc && rc.on_env) {
40
47
  const patched_env = await rc.on_env(env);
41
- return typeof patched_env === 'undefined' ? env : (patched_env as EnvData);
48
+ if (typeof patched_env === 'undefined') return env;
49
+ if (!patched_env || typeof patched_env !== 'object' || Array.isArray(patched_env)) {
50
+ throw new Error(
51
+ `[rncu] on_env hook must return an object (or undefined to keep the original), ` +
52
+ `but got ${typeof patched_env}: ${String(patched_env)}`
53
+ );
54
+ }
55
+ return patched_env as EnvData;
42
56
  } else {
43
57
  return env;
44
58
  }
@@ -4,13 +4,4 @@ const IOS_DATA = {{{toJSON @root.ios}}}
4
4
 
5
5
  const ANDROID_DATA = {{{toJSON @root.android}}}
6
6
 
7
- module.exports = {
8
- {{#each @root.ios}}
9
- get {{{@key}}}() {
10
- return RN.Platform.select({
11
- ios: IOS_DATA["{{{@key}}}"],
12
- android: ANDROID_DATA["{{{@key}}}"],
13
- });
14
- },
15
- {{/each}}
16
- }
7
+ module.exports = RN.Platform.select({ ios: IOS_DATA, android: ANDROID_DATA }) || {};
@@ -3,16 +3,14 @@ import type { EnvData, Schema } from './resolve-env';
3
3
  const VALID_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
4
4
 
5
5
  /**
6
- * Validate env data against a schema defined in `.rncurc.js`.
7
- * Called after `on_env` so the hook can add/transform vars before validation.
6
+ * Validate that all env key names are valid C/Java/JS identifiers.
7
+ * This runs UNCONDITIONALLY (even without a schema) to prevent template
8
+ * injection in generated native files (ConfigValues.h, xcconfig, override.js).
8
9
  *
9
- * Throws with a human-readable error listing ALL failures at once
10
- * (not just the first one), so users can fix everything in one pass.
10
+ * Throws with a human-readable error listing ALL invalid keys at once.
11
11
  */
12
- export function validate_env(env: EnvData, schema: Schema): void {
12
+ export function validate_keys(env: EnvData): void {
13
13
  const errors: string[] = [];
14
-
15
- // Validate all env key names are valid identifiers
16
14
  for (const key of Object.keys(env)) {
17
15
  if (!VALID_KEY_PATTERN.test(key)) {
18
16
  errors.push(
@@ -20,6 +18,24 @@ export function validate_env(env: EnvData, schema: Schema): void {
20
18
  );
21
19
  }
22
20
  }
21
+ if (errors.length > 0) {
22
+ throw new Error(
23
+ `\n\n❌ react-native-config-ultimate: invalid key names:\n` +
24
+ errors.map((e) => ` • ${e}`).join('\n') +
25
+ '\n'
26
+ );
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Validate env data against a schema defined in `.rncurc.js`.
32
+ * Called after `on_env` so the hook can add/transform vars before validation.
33
+ *
34
+ * Throws with a human-readable error listing ALL failures at once
35
+ * (not just the first one), so users can fix everything in one pass.
36
+ */
37
+ export function validate_env(env: EnvData, schema: Schema): void {
38
+ const errors: string[] = [];
23
39
 
24
40
  // Pre-compile all regex patterns once, before iterating over env values.
25
41
  // This avoids re-compiling the same pattern for every validated key.
package/android/rnuc.yaml DELETED
@@ -1 +0,0 @@
1
- HELLO: "world"
@@ -1,50 +0,0 @@
1
- "use strict";
2
-
3
- var _child_process = _interopRequireDefault(require("child_process"));
4
- var _fs = _interopRequireDefault(require("fs"));
5
- var _path = _interopRequireDefault(require("path"));
6
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
7
- // eslint-disable-next-line @typescript-eslint/no-require-imports
8
- const {
9
- files_to_assert
10
- } = require('./main.spec');
11
-
12
- // TODO: Re-enable after fixing integration test paths for bob build output
13
- describe.skip.each`
14
- extension | env_test_content
15
- ${''} | ${'hello=world'}
16
- ${'.yaml'} | ${'hello: world'}
17
- ${'.yml'} | ${'hello: world'}
18
- `('test codegen', ({
19
- extension,
20
- env_test_content
21
- }) => {
22
- let project_root;
23
- beforeAll(() => {
24
- project_root = _path.default.join(process.cwd(), _fs.default.mkdtempSync('rncu-jest'));
25
- for (const file_path of files_to_assert) {
26
- const {
27
- dir
28
- } = _path.default.parse(file_path);
29
- const folder = _path.default.join(project_root, dir);
30
- _fs.default.mkdirSync(folder, {
31
- recursive: true
32
- });
33
- }
34
- });
35
- afterAll(() => {
36
- _fs.default.rmSync(project_root, {
37
- recursive: true,
38
- force: true
39
- });
40
- });
41
- it.each(files_to_assert.map(k => [k]))('creates file at path %s', file_path => {
42
- const env_file_path = _path.default.join(project_root, `.env${extension}`);
43
- _fs.default.writeFileSync(env_file_path, env_test_content);
44
- _child_process.default.execFileSync('node', [_path.default.join(process.cwd(), 'lib/commonjs/cli.js'), env_file_path], {
45
- cwd: project_root
46
- });
47
- expect(_fs.default.existsSync(_path.default.join(project_root, file_path))).toEqual(true);
48
- });
49
- });
50
- //# sourceMappingURL=bin.spec.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["_child_process","_interopRequireDefault","require","_fs","_path","e","__esModule","default","files_to_assert","describe","skip","each","extension","env_test_content","project_root","beforeAll","path","join","process","cwd","fs","mkdtempSync","file_path","dir","parse","folder","mkdirSync","recursive","afterAll","rmSync","force","it","map","k","env_file_path","writeFileSync","cp","execFileSync","expect","existsSync","toEqual"],"sourceRoot":"../../src","sources":["bin.spec.ts"],"mappings":";;AAAA,IAAAA,cAAA,GAAAC,sBAAA,CAAAC,OAAA;AACA,IAAAC,GAAA,GAAAF,sBAAA,CAAAC,OAAA;AACA,IAAAE,KAAA,GAAAH,sBAAA,CAAAC,OAAA;AAAwB,SAAAD,uBAAAI,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA;AACxB;AACA,MAAM;EAAEG;AAAgB,CAAC,GAAGN,OAAO,CAAC,aAAa,CAAkC;;AAEnF;AACAO,QAAQ,CAACC,IAAI,CAACC,IAAI;AAClB;AACA,IAAI,EAAE,WAAW,aAAa;AAC9B,IAAI,OAAO,MAAM,cAAc;AAC/B,IAAI,MAAM,OAAO,cAAc;AAC/B,CAAC,CACC,cAAc,EACd,CAAC;EAAEC,SAAS;EAAEC;AAAkE,CAAC,KAAK;EACpF,IAAIC,YAAoB;EACxBC,SAAS,CAAC,MAAM;IACdD,YAAY,GAAGE,aAAI,CAACC,IAAI,CAACC,OAAO,CAACC,GAAG,CAAC,CAAC,EAAEC,WAAE,CAACC,WAAW,CAAC,WAAW,CAAC,CAAC;IACpE,KAAK,MAAMC,SAAS,IAAId,eAAe,EAAE;MACvC,MAAM;QAAEe;MAAI,CAAC,GAAGP,aAAI,CAACQ,KAAK,CAACF,SAAS,CAAC;MACrC,MAAMG,MAAM,GAAGT,aAAI,CAACC,IAAI,CAACH,YAAY,EAAES,GAAG,CAAC;MAC3CH,WAAE,CAACM,SAAS,CAACD,MAAM,EAAE;QAAEE,SAAS,EAAE;MAAK,CAAC,CAAC;IAC3C;EACF,CAAC,CAAC;EACFC,QAAQ,CAAC,MAAM;IACbR,WAAE,CAACS,MAAM,CAACf,YAAY,EAAE;MAAEa,SAAS,EAAE,IAAI;MAAEG,KAAK,EAAE;IAAK,CAAC,CAAC;EAC3D,CAAC,CAAC;EACFC,EAAE,CAACpB,IAAI,CAACH,eAAe,CAACwB,GAAG,CAAEC,CAAC,IAAK,CAACA,CAAC,CAAC,CAAC,CAAC,CAAC,yBAAyB,EAAGX,SAAS,IAAK;IACjF,MAAMY,aAAa,GAAGlB,aAAI,CAACC,IAAI,CAACH,YAAY,EAAE,OAAOF,SAAS,EAAE,CAAC;IACjEQ,WAAE,CAACe,aAAa,CAACD,aAAa,EAAErB,gBAAgB,CAAC;IACjDuB,sBAAE,CAACC,YAAY,CAAC,MAAM,EAAE,CAACrB,aAAI,CAACC,IAAI,CAACC,OAAO,CAACC,GAAG,CAAC,CAAC,EAAE,qBAAqB,CAAC,EAAEe,aAAa,CAAC,EAAE;MACxFf,GAAG,EAAEL;IACP,CAAC,CAAC;IACFwB,MAAM,CAAClB,WAAE,CAACmB,UAAU,CAACvB,aAAI,CAACC,IAAI,CAACH,YAAY,EAAEQ,SAAmB,CAAC,CAAC,CAAC,CAACkB,OAAO,CAAC,IAAI,CAAC;EACnF,CAAC,CAAC;AACJ,CACF,CAAC","ignoreList":[]}
@@ -1,190 +0,0 @@
1
- "use strict";
2
-
3
- // All mocks must be declared before the module is required.
4
- const mock_main = jest.fn();
5
- jest.doMock('./main', () => ({
6
- __esModule: true,
7
- default: mock_main
8
- }));
9
- const mock_exists_sync = jest.fn();
10
- jest.doMock('fs', () => ({
11
- existsSync: mock_exists_sync
12
- }));
13
-
14
- // Mock watcher returned by chokidar.watch()
15
- const mock_watcher_on = jest.fn();
16
- const mock_watcher_close = jest.fn().mockResolvedValue(undefined);
17
- const mock_watcher = {
18
- on: mock_watcher_on.mockReturnThis(),
19
- close: mock_watcher_close
20
- };
21
- const mock_chokidar_watch = jest.fn().mockReturnValue(mock_watcher);
22
- jest.doMock('chokidar', () => ({
23
- watch: mock_chokidar_watch
24
- }));
25
-
26
- // eslint-disable-next-line @typescript-eslint/no-require-imports
27
- const cli = require('./cli').default;
28
-
29
- // ─── helpers ────────────────────────────────────────────────────────────────
30
-
31
- function set_argv(...args) {
32
- process.argv = ['node', 'rncu', ...args];
33
- }
34
-
35
- /** Grab the handler registered for a given chokidar event. */
36
- function get_watcher_handler(event) {
37
- const call = mock_watcher_on.mock.calls.find(([ev]) => ev === event);
38
- return call?.[1];
39
- }
40
-
41
- // ─── test suite ──────────────────────────────────────────────────────────────
42
-
43
- describe('cli', () => {
44
- const original_argv = process.argv;
45
- const stdin_resume_spy = jest.spyOn(process.stdin, 'resume').mockImplementation(() => process.stdin);
46
- const process_on_spy = jest.spyOn(process, 'on').mockImplementation(() => process);
47
- beforeEach(() => {
48
- mock_main.mockReset().mockResolvedValue(undefined);
49
- // Default: env files exist, RC file does NOT exist.
50
- // This mimics the real-world baseline: user passes a valid .env file,
51
- // but hasn't created a .rncurc.js config yet.
52
- mock_exists_sync.mockReset().mockImplementation(p => !p.endsWith('.rncurc.js'));
53
- mock_chokidar_watch.mockReset().mockReturnValue(mock_watcher);
54
- mock_watcher_on.mockReset().mockReturnThis();
55
- mock_watcher_close.mockReset().mockResolvedValue(undefined);
56
- stdin_resume_spy.mockClear();
57
- process_on_spy.mockClear();
58
- });
59
- afterAll(() => {
60
- process.argv = original_argv;
61
- stdin_resume_spy.mockRestore();
62
- process_on_spy.mockRestore();
63
- });
64
-
65
- // ── normal (non-watch) mode ─────────────────────────────────────────────
66
-
67
- describe('normal mode', () => {
68
- it('runs main once with the env file and exits', async () => {
69
- set_argv('.env');
70
- await cli();
71
- expect(mock_main).toHaveBeenCalledTimes(1);
72
- expect(mock_main).toHaveBeenCalledWith(expect.any(String), expect.any(String), ['.env'], undefined);
73
- expect(mock_chokidar_watch).not.toHaveBeenCalled();
74
- });
75
- it('passes multiple env files to main', async () => {
76
- set_argv('.env.base', '.env.staging');
77
- await cli();
78
- expect(mock_main).toHaveBeenCalledWith(expect.any(String), expect.any(String), ['.env.base', '.env.staging'], undefined);
79
- });
80
- it('loads and passes RC file when it exists', async () => {
81
- set_argv('.env');
82
- // All files exist (env file + RC file). The RC file can't actually be
83
- // resolved via require() in test env, so cli() is expected to throw.
84
- mock_exists_sync.mockReturnValue(true);
85
- await expect(cli()).rejects.toThrow();
86
- });
87
- it('propagates errors from main in non-watch mode', async () => {
88
- set_argv('.env');
89
- mock_main.mockRejectedValueOnce(new Error('missing var'));
90
- await expect(cli()).rejects.toThrow('missing var');
91
- });
92
- });
93
-
94
- // ── watch mode ──────────────────────────────────────────────────────────
95
-
96
- describe('--watch mode', () => {
97
- it('starts chokidar watcher on the env files', async () => {
98
- set_argv('.env', '--watch');
99
- await cli();
100
- expect(mock_chokidar_watch).toHaveBeenCalledWith(['.env'], expect.objectContaining({
101
- ignoreInitial: true,
102
- persistent: true
103
- }));
104
- });
105
- it('also watches .rncurc.js when it exists', async () => {
106
- set_argv('.env', '--watch');
107
- // All files exist (env + RC). We suppress the require() error below
108
- // so the test can verify that .rncurc.js is added to the watcher list.
109
- mock_exists_sync.mockReturnValue(true);
110
- // Suppress require() failure for missing rc file in initial run
111
- mock_main.mockResolvedValue(undefined);
112
- try {
113
- await cli();
114
- } catch {
115
- // ignore require error for non-existent RC in test env
116
- }
117
- const watched_files = mock_chokidar_watch.mock.calls[0]?.[0];
118
- expect(watched_files.some(f => f.endsWith('.rncurc.js'))).toBe(true);
119
- });
120
- it('runs main once immediately on start', async () => {
121
- set_argv('.env', '--watch');
122
- await cli();
123
- expect(mock_main).toHaveBeenCalledTimes(1);
124
- });
125
- it('registers change and add event handlers', async () => {
126
- set_argv('.env', '--watch');
127
- await cli();
128
- const events = mock_watcher_on.mock.calls.map(([ev]) => ev);
129
- expect(events).toContain('change');
130
- expect(events).toContain('add');
131
- });
132
- it('re-runs main when a file changes', async () => {
133
- set_argv('.env', '--watch');
134
- await cli();
135
- expect(mock_main).toHaveBeenCalledTimes(1);
136
- const on_change = get_watcher_handler('change');
137
- expect(on_change).toBeDefined();
138
- await on_change?.('.env');
139
- expect(mock_main).toHaveBeenCalledTimes(2);
140
- });
141
- it('re-runs main when a file is added', async () => {
142
- set_argv('.env', '--watch');
143
- await cli();
144
- const on_add = get_watcher_handler('add');
145
- await on_add?.('.env.local');
146
- expect(mock_main).toHaveBeenCalledTimes(2);
147
- });
148
- it('catches errors on re-run and keeps watching (does not throw)', async () => {
149
- set_argv('.env', '--watch');
150
- await cli();
151
- mock_main.mockRejectedValueOnce(new Error('validation failed'));
152
- const on_change = get_watcher_handler('change');
153
- expect(on_change).toBeDefined();
154
-
155
- // The handler uses `void run(p)` so it returns undefined synchronously
156
- // and swallows errors inside run()'s try/catch. We trigger it and then
157
- // drain the microtask queue to let the async error handling complete.
158
- expect(() => on_change('.env')).not.toThrow();
159
- await new Promise(resolve => setImmediate(resolve));
160
-
161
- // main was called twice: initial run + change handler
162
- expect(mock_main).toHaveBeenCalledTimes(2);
163
- });
164
- it('catches initial run errors and still starts the watcher', async () => {
165
- set_argv('.env', '--watch');
166
- mock_main.mockRejectedValueOnce(new Error('initial run failed'));
167
-
168
- // Should not throw even though initial run fails
169
- await expect(cli()).resolves.toBeUndefined();
170
- expect(mock_chokidar_watch).toHaveBeenCalled();
171
- });
172
- it('keeps process alive via process.stdin.resume()', async () => {
173
- set_argv('.env', '--watch');
174
- await cli();
175
- expect(stdin_resume_spy).toHaveBeenCalled();
176
- });
177
- it('registers a SIGINT handler for graceful shutdown', async () => {
178
- set_argv('.env', '--watch');
179
- await cli();
180
- const sigint_call = process_on_spy.mock.calls.find(([event]) => event === 'SIGINT');
181
- expect(sigint_call).toBeDefined();
182
- });
183
- it('uses -w as alias for --watch', async () => {
184
- set_argv('.env', '-w');
185
- await cli();
186
- expect(mock_chokidar_watch).toHaveBeenCalled();
187
- });
188
- });
189
- });
190
- //# sourceMappingURL=cli.spec.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["mock_main","jest","fn","doMock","__esModule","default","mock_exists_sync","existsSync","mock_watcher_on","mock_watcher_close","mockResolvedValue","undefined","mock_watcher","on","mockReturnThis","close","mock_chokidar_watch","mockReturnValue","watch","cli","require","set_argv","args","process","argv","get_watcher_handler","event","call","mock","calls","find","ev","describe","original_argv","stdin_resume_spy","spyOn","stdin","mockImplementation","process_on_spy","beforeEach","mockReset","p","endsWith","mockClear","afterAll","mockRestore","it","expect","toHaveBeenCalledTimes","toHaveBeenCalledWith","any","String","not","toHaveBeenCalled","rejects","toThrow","mockRejectedValueOnce","Error","objectContaining","ignoreInitial","persistent","watched_files","some","f","toBe","events","map","toContain","on_change","toBeDefined","on_add","Promise","resolve","setImmediate","resolves","toBeUndefined","sigint_call"],"sourceRoot":"../../src","sources":["cli.spec.ts"],"mappings":"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","ignoreList":[]}
@@ -1,32 +0,0 @@
1
- "use strict";
2
-
3
- var _flatten = _interopRequireDefault(require("./flatten"));
4
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
5
- describe('flatten', () => {
6
- it('flattens config per platform', () => {
7
- const config = {
8
- value1: 'abc',
9
- value2: {
10
- ios: 'def',
11
- android: 'xyz',
12
- web: '123'
13
- }
14
- };
15
- const flat_ios = (0, _flatten.default)(config, 'ios');
16
- expect(flat_ios).toEqual({
17
- value1: 'abc',
18
- value2: 'def'
19
- });
20
- const flat_android = (0, _flatten.default)(config, 'android');
21
- expect(flat_android).toEqual({
22
- value1: 'abc',
23
- value2: 'xyz'
24
- });
25
- const flat_web = (0, _flatten.default)(config, 'web');
26
- expect(flat_web).toEqual({
27
- value1: 'abc',
28
- value2: '123'
29
- });
30
- });
31
- });
32
- //# sourceMappingURL=flatten.spec.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["_flatten","_interopRequireDefault","require","e","__esModule","default","describe","it","config","value1","value2","ios","android","web","flat_ios","flatten","expect","toEqual","flat_android","flat_web"],"sourceRoot":"../../src","sources":["flatten.spec.ts"],"mappings":";;AAAA,IAAAA,QAAA,GAAAC,sBAAA,CAAAC,OAAA;AAAgC,SAAAD,uBAAAE,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA;AAEhCG,QAAQ,CAAC,SAAS,EAAE,MAAM;EACxBC,EAAE,CAAC,8BAA8B,EAAE,MAAM;IACvC,MAAMC,MAAM,GAAG;MACbC,MAAM,EAAE,KAAK;MACbC,MAAM,EAAE;QAAEC,GAAG,EAAE,KAAK;QAAEC,OAAO,EAAE,KAAK;QAAEC,GAAG,EAAE;MAAM;IACnD,CAAC;IACD,MAAMC,QAAQ,GAAG,IAAAC,gBAAO,EAACP,MAAM,EAAE,KAAK,CAAC;IACvCQ,MAAM,CAACF,QAAQ,CAAC,CAACG,OAAO,CAAC;MAAER,MAAM,EAAE,KAAK;MAAEC,MAAM,EAAE;IAAM,CAAC,CAAC;IAC1D,MAAMQ,YAAY,GAAG,IAAAH,gBAAO,EAACP,MAAM,EAAE,SAAS,CAAC;IAC/CQ,MAAM,CAACE,YAAY,CAAC,CAACD,OAAO,CAAC;MAAER,MAAM,EAAE,KAAK;MAAEC,MAAM,EAAE;IAAM,CAAC,CAAC;IAC9D,MAAMS,QAAQ,GAAG,IAAAJ,gBAAO,EAACP,MAAM,EAAE,KAAK,CAAC;IACvCQ,MAAM,CAACG,QAAQ,CAAC,CAACF,OAAO,CAAC;MAAER,MAAM,EAAE,KAAK;MAAEC,MAAM,EAAE;IAAM,CAAC,CAAC;EAC5D,CAAC,CAAC;AACJ,CAAC,CAAC","ignoreList":[]}
@@ -1,257 +0,0 @@
1
- "use strict";
2
-
3
- const mockReadFileSync = jest.fn();
4
- jest.doMock('fs', () => ({
5
- readFileSync: mockReadFileSync
6
- }));
7
- const mockParse = jest.fn();
8
- jest.doMock('dotenv', () => ({
9
- parse: mockParse
10
- }));
11
- const mockExpand = jest.fn();
12
- jest.doMock('dotenv-expand', () => ({
13
- expand: mockExpand
14
- }));
15
- const mockYaml = jest.fn();
16
- jest.doMock('js-yaml', () => ({
17
- load: mockYaml
18
- }));
19
-
20
- // eslint-disable-next-line @typescript-eslint/no-require-imports
21
- const load_env = require('./load-env').default;
22
- describe('load-env', () => {
23
- beforeEach(() => {
24
- mockReadFileSync.mockReset();
25
- mockParse.mockReset();
26
- mockExpand.mockReset();
27
- mockYaml.mockReset();
28
- // Default expand: return parsed as-is (no expansion side effects)
29
- mockExpand.mockImplementation(input => input);
30
- });
31
- describe('dotenv format', () => {
32
- it('reads a single dotenv file (backward-compatible string arg)', () => {
33
- mockReadFileSync.mockReturnValueOnce('hello=world');
34
- mockParse.mockReturnValueOnce({
35
- hello: 'world'
36
- });
37
- const result = load_env('hello');
38
- expect(mockReadFileSync).toHaveBeenCalledWith('hello', 'utf8');
39
- expect(mockParse).toHaveBeenCalledWith('hello=world');
40
- expect(result).toEqual({
41
- hello: 'world'
42
- });
43
- });
44
- it('reads a single dotenv file when passed as an array', () => {
45
- mockReadFileSync.mockReturnValueOnce('hello=world');
46
- mockParse.mockReturnValueOnce({
47
- hello: 'world'
48
- });
49
- const result = load_env(['hello']);
50
- expect(mockReadFileSync).toHaveBeenCalledWith('hello', 'utf8');
51
- expect(result).toEqual({
52
- hello: 'world'
53
- });
54
- });
55
- it('merges multiple dotenv files, last file wins for conflicts', () => {
56
- mockReadFileSync.mockReturnValueOnce('A=base\nB=base').mockReturnValueOnce('B=override\nC=new');
57
- mockParse.mockReturnValueOnce({
58
- A: 'base',
59
- B: 'base'
60
- }).mockReturnValueOnce({
61
- B: 'override',
62
- C: 'new'
63
- });
64
- const result = load_env(['.env.base', '.env.staging']);
65
- expect(mockReadFileSync).toHaveBeenCalledTimes(2);
66
- // expand is called once with the merged raw object
67
- expect(mockExpand).toHaveBeenCalledWith({
68
- parsed: {
69
- A: 'base',
70
- B: 'override',
71
- C: 'new'
72
- }
73
- });
74
- expect(result).toEqual({
75
- A: 'base',
76
- B: 'override',
77
- C: 'new'
78
- });
79
- });
80
- it('expands $VAR references using dotenv-expand', () => {
81
- mockReadFileSync.mockReturnValueOnce('BASE=https://api.com\nURL=$BASE/v1');
82
- mockParse.mockReturnValueOnce({
83
- BASE: 'https://api.com',
84
- URL: '$BASE/v1'
85
- });
86
- mockExpand.mockReturnValueOnce({
87
- parsed: {
88
- BASE: 'https://api.com',
89
- URL: 'https://api.com/v1'
90
- }
91
- });
92
- const result = load_env('.env');
93
- expect(result).toEqual({
94
- BASE: 'https://api.com',
95
- URL: 'https://api.com/v1'
96
- });
97
- });
98
- it('expands cross-file $VAR references when merging multiple files', () => {
99
- mockReadFileSync.mockReturnValueOnce('BASE_URL=https://api.com').mockReturnValueOnce('API_URL=$BASE_URL/v1');
100
- mockParse.mockReturnValueOnce({
101
- BASE_URL: 'https://api.com'
102
- }).mockReturnValueOnce({
103
- API_URL: '$BASE_URL/v1'
104
- });
105
- // Expand is called with merged raw — so cross-file reference resolves
106
- mockExpand.mockReturnValueOnce({
107
- parsed: {
108
- BASE_URL: 'https://api.com',
109
- API_URL: 'https://api.com/v1'
110
- }
111
- });
112
- const result = load_env(['.env.base', '.env.staging']);
113
- expect(mockExpand).toHaveBeenCalledWith({
114
- parsed: {
115
- BASE_URL: 'https://api.com',
116
- API_URL: '$BASE_URL/v1'
117
- }
118
- });
119
- expect(result).toEqual({
120
- BASE_URL: 'https://api.com',
121
- API_URL: 'https://api.com/v1'
122
- });
123
- });
124
- });
125
- describe('yaml format', () => {
126
- it.each`
127
- extension
128
- ${'yml'}
129
- ${'yaml'}
130
- `("reads yaml when extension is '.$extension'", ({
131
- extension
132
- }) => {
133
- mockReadFileSync.mockReturnValueOnce(Buffer.from('data'));
134
- mockYaml.mockReturnValueOnce({
135
- hello: 'world'
136
- });
137
- const result = load_env(`hello.${extension}`);
138
- expect(mockReadFileSync).toHaveBeenCalledWith(`hello.${extension}`);
139
- expect(mockYaml).toHaveBeenCalledWith('data');
140
- expect(result).toEqual({
141
- hello: 'world'
142
- });
143
- });
144
- it('merges multiple yaml files, last file wins for conflicts', () => {
145
- mockReadFileSync.mockReturnValueOnce(Buffer.from('A: base\nB: base')).mockReturnValueOnce(Buffer.from('B: override\nC: new'));
146
- mockYaml.mockReturnValueOnce({
147
- A: 'base',
148
- B: 'base'
149
- }).mockReturnValueOnce({
150
- B: 'override',
151
- C: 'new'
152
- });
153
- const result = load_env(['base.yaml', 'staging.yaml']);
154
- expect(mockReadFileSync).toHaveBeenCalledTimes(2);
155
- expect(result).toEqual({
156
- A: 'base',
157
- B: 'override',
158
- C: 'new'
159
- });
160
- });
161
- describe.each`
162
- extension
163
- ${'yml'}
164
- ${'yaml'}
165
- `("throws when yaml is not an object with extension '.$extension'", ({
166
- extension
167
- }) => {
168
- it.each`
169
- content
170
- ${'abc:def'}
171
- ${false}
172
- ${true}
173
- ${42}
174
- ${null}
175
- ${undefined}
176
- `("when content is '$content'", ({
177
- content
178
- }) => {
179
- mockReadFileSync.mockReturnValueOnce(Buffer.from('data'));
180
- mockYaml.mockReturnValueOnce(content);
181
- expect(() => {
182
- load_env(`hello.${extension}`);
183
- }).toThrow();
184
- });
185
- });
186
- });
187
- describe('edge cases', () => {
188
- it('throws when no files are provided', () => {
189
- expect(() => load_env([])).toThrow('No env file specified');
190
- });
191
- });
192
- describe('mixed format (yaml + dotenv)', () => {
193
- it('merges yaml and dotenv files together', () => {
194
- // First file is yaml
195
- mockReadFileSync.mockReturnValueOnce(Buffer.from('YAML_VAR: from_yaml')).mockReturnValueOnce('DOTENV_VAR=from_dotenv');
196
- mockYaml.mockReturnValueOnce({
197
- YAML_VAR: 'from_yaml'
198
- });
199
- mockParse.mockReturnValueOnce({
200
- DOTENV_VAR: 'from_dotenv'
201
- });
202
- const result = load_env(['config.yaml', '.env']);
203
- expect(mockYaml).toHaveBeenCalled();
204
- expect(mockParse).toHaveBeenCalled();
205
- expect(result).toEqual({
206
- YAML_VAR: 'from_yaml',
207
- DOTENV_VAR: 'from_dotenv'
208
- });
209
- });
210
- it('dotenv file in mixed mode still gets expanded', () => {
211
- mockReadFileSync.mockReturnValueOnce(Buffer.from('BASE: https://api.com')).mockReturnValueOnce('URL=$BASE/v1');
212
- mockYaml.mockReturnValueOnce({
213
- BASE: 'https://api.com'
214
- });
215
- mockParse.mockReturnValueOnce({
216
- URL: '$BASE/v1'
217
- });
218
- // In mixed mode, expand is called per-dotenv-file
219
- mockExpand.mockReturnValueOnce({
220
- parsed: {
221
- URL: 'https://api.com/v1'
222
- }
223
- });
224
- const result = load_env(['config.yaml', '.env']);
225
- expect(result).toEqual({
226
- BASE: 'https://api.com',
227
- URL: 'https://api.com/v1'
228
- });
229
- });
230
- it('last file wins for conflicting keys in mixed mode', () => {
231
- mockReadFileSync.mockReturnValueOnce(Buffer.from('SHARED: from_yaml')).mockReturnValueOnce('SHARED=from_dotenv');
232
- mockYaml.mockReturnValueOnce({
233
- SHARED: 'from_yaml'
234
- });
235
- mockParse.mockReturnValueOnce({
236
- SHARED: 'from_dotenv'
237
- });
238
- const result = load_env(['config.yaml', '.env']);
239
- expect(result.SHARED).toBe('from_dotenv');
240
- });
241
- it('handles dotenv first, then yaml', () => {
242
- mockReadFileSync.mockReturnValueOnce('DOTENV_VAR=first').mockReturnValueOnce(Buffer.from('YAML_VAR: second'));
243
- mockParse.mockReturnValueOnce({
244
- DOTENV_VAR: 'first'
245
- });
246
- mockYaml.mockReturnValueOnce({
247
- YAML_VAR: 'second'
248
- });
249
- const result = load_env(['.env', 'config.yml']);
250
- expect(result).toEqual({
251
- DOTENV_VAR: 'first',
252
- YAML_VAR: 'second'
253
- });
254
- });
255
- });
256
- });
257
- //# sourceMappingURL=load-env.spec.js.map