@geekmidas/cli 1.4.0 → 1.5.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.
- package/CHANGELOG.md +12 -0
- package/dist/{HostingerProvider-B9N-TKbp.mjs → HostingerProvider-402UdK89.mjs} +34 -1
- package/dist/HostingerProvider-402UdK89.mjs.map +1 -0
- package/dist/{HostingerProvider-DUV9-Tzg.cjs → HostingerProvider-BiXdHjiq.cjs} +34 -1
- package/dist/HostingerProvider-BiXdHjiq.cjs.map +1 -0
- package/dist/{Route53Provider-DOWmFnwN.mjs → Route53Provider-DbBo7Uz5.mjs} +55 -2
- package/dist/Route53Provider-DbBo7Uz5.mjs.map +1 -0
- package/dist/{Route53Provider-xrWuBXih.cjs → Route53Provider-kfJ77LmL.cjs} +55 -2
- package/dist/Route53Provider-kfJ77LmL.cjs.map +1 -0
- package/dist/backup-provisioner-B5e-F6zX.cjs +164 -0
- package/dist/backup-provisioner-B5e-F6zX.cjs.map +1 -0
- package/dist/backup-provisioner-BIArpmTr.mjs +163 -0
- package/dist/backup-provisioner-BIArpmTr.mjs.map +1 -0
- package/dist/{config-C1dM7aZb.cjs → config-BYn5yUt5.cjs} +2 -2
- package/dist/{config-C1dM7aZb.cjs.map → config-BYn5yUt5.cjs.map} +1 -1
- package/dist/{config-C1bidhvG.mjs → config-dLNQIvDR.mjs} +2 -2
- package/dist/{config-C1bidhvG.mjs.map → config-dLNQIvDR.mjs.map} +1 -1
- package/dist/config.cjs +2 -2
- package/dist/config.d.cts +1 -1
- package/dist/config.d.mts +2 -2
- package/dist/config.mjs +2 -2
- package/dist/{dokploy-api-z0833e7r.mjs → dokploy-api-2ldYoN3i.mjs} +131 -1
- package/dist/dokploy-api-2ldYoN3i.mjs.map +1 -0
- package/dist/dokploy-api-C93pveuy.mjs +3 -0
- package/dist/dokploy-api-CbDh4o93.cjs +3 -0
- package/dist/{dokploy-api-CQvhV6Hd.cjs → dokploy-api-DLgvEQlr.cjs} +131 -1
- package/dist/dokploy-api-DLgvEQlr.cjs.map +1 -0
- package/dist/{index-DzmZ6SUW.d.cts → index-Ba21_lNt.d.cts} +157 -29
- package/dist/index-Ba21_lNt.d.cts.map +1 -0
- package/dist/{index-DvpWzLD7.d.mts → index-Bj5VNxEL.d.mts} +158 -30
- package/dist/index-Bj5VNxEL.d.mts.map +1 -0
- package/dist/index.cjs +219 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +219 -68
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-9k6a6VA4.mjs → openapi-CMTyaIJJ.mjs} +2 -2
- package/dist/{openapi-9k6a6VA4.mjs.map → openapi-CMTyaIJJ.mjs.map} +1 -1
- package/dist/{openapi-Dcja4e1C.cjs → openapi-CqblwJZ4.cjs} +2 -2
- package/dist/{openapi-Dcja4e1C.cjs.map → openapi-CqblwJZ4.cjs.map} +1 -1
- package/dist/openapi.cjs +3 -3
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.mjs +3 -3
- package/dist/{types-B9UZ7fOG.d.mts → types-CZg5iUgD.d.mts} +1 -1
- package/dist/{types-B9UZ7fOG.d.mts.map → types-CZg5iUgD.d.mts.map} +1 -1
- package/dist/workspace/index.cjs +1 -1
- package/dist/workspace/index.d.cts +1 -1
- package/dist/workspace/index.d.mts +2 -2
- package/dist/workspace/index.mjs +1 -1
- package/dist/{workspace-CeFgIDC-.cjs → workspace-DIMnYaYt.cjs} +20 -2
- package/dist/{workspace-CeFgIDC-.cjs.map → workspace-DIMnYaYt.cjs.map} +1 -1
- package/dist/{workspace-Cb_I7oCJ.mjs → workspace-Dy8k7Wru.mjs} +20 -2
- package/dist/{workspace-Cb_I7oCJ.mjs.map → workspace-Dy8k7Wru.mjs.map} +1 -1
- package/examples/cron-example.ts +6 -6
- package/examples/function-example.ts +1 -1
- package/package.json +7 -5
- package/src/deploy/__tests__/Route53Provider.spec.ts +23 -0
- package/src/deploy/__tests__/backup-provisioner.spec.ts +428 -0
- package/src/deploy/__tests__/createDnsProvider.spec.ts +23 -0
- package/src/deploy/__tests__/env-resolver.spec.ts +239 -0
- package/src/deploy/__tests__/sniffer.spec.ts +104 -93
- package/src/deploy/__tests__/undeploy.spec.ts +758 -0
- package/src/deploy/backup-provisioner.ts +316 -0
- package/src/deploy/dns/DnsProvider.ts +39 -1
- package/src/deploy/dns/HostingerProvider.ts +74 -0
- package/src/deploy/dns/Route53Provider.ts +85 -1
- package/src/deploy/dns/index.ts +25 -0
- package/src/deploy/dokploy-api.ts +237 -0
- package/src/deploy/env-resolver.ts +11 -1
- package/src/deploy/index.ts +143 -37
- package/src/deploy/sniffer.ts +39 -7
- package/src/deploy/state.ts +171 -0
- package/src/deploy/undeploy.ts +407 -0
- package/src/generators/FunctionGenerator.ts +1 -1
- package/src/init/generators/monorepo.ts +4 -0
- package/src/init/generators/web.ts +45 -2
- package/src/init/versions.ts +2 -2
- package/src/workspace/schema.ts +34 -0
- package/src/workspace/types.ts +37 -37
- package/dist/HostingerProvider-B9N-TKbp.mjs.map +0 -1
- package/dist/HostingerProvider-DUV9-Tzg.cjs.map +0 -1
- package/dist/Route53Provider-DOWmFnwN.mjs.map +0 -1
- package/dist/Route53Provider-xrWuBXih.cjs.map +0 -1
- package/dist/dokploy-api-CQvhV6Hd.cjs.map +0 -1
- package/dist/dokploy-api-CWc02yyg.cjs +0 -3
- package/dist/dokploy-api-DSJYNx88.mjs +0 -3
- package/dist/dokploy-api-z0833e7r.mjs.map +0 -1
- package/dist/index-DvpWzLD7.d.mts.map +0 -1
- package/dist/index-DzmZ6SUW.d.cts.map +0 -1
|
@@ -452,6 +452,71 @@ describe('resolveEnvVar', () => {
|
|
|
452
452
|
'https://auth.example.com',
|
|
453
453
|
);
|
|
454
454
|
});
|
|
455
|
+
|
|
456
|
+
describe('NEXT_PUBLIC_ prefix', () => {
|
|
457
|
+
it('should resolve NEXT_PUBLIC_API_URL from dependencyUrls.api', () => {
|
|
458
|
+
const context = createContext({
|
|
459
|
+
dependencyUrls: { api: 'https://api.example.com' },
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
expect(resolveEnvVar('NEXT_PUBLIC_API_URL', context)).toBe(
|
|
463
|
+
'https://api.example.com',
|
|
464
|
+
);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it('should resolve NEXT_PUBLIC_AUTH_URL from dependencyUrls.auth', () => {
|
|
468
|
+
const context = createContext({
|
|
469
|
+
dependencyUrls: { auth: 'https://auth.example.com' },
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
expect(resolveEnvVar('NEXT_PUBLIC_AUTH_URL', context)).toBe(
|
|
473
|
+
'https://auth.example.com',
|
|
474
|
+
);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
it('should resolve both AUTH_URL and NEXT_PUBLIC_AUTH_URL to same value', () => {
|
|
478
|
+
const context = createContext({
|
|
479
|
+
dependencyUrls: { auth: 'https://auth.example.com' },
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
expect(resolveEnvVar('AUTH_URL', context)).toBe(
|
|
483
|
+
'https://auth.example.com',
|
|
484
|
+
);
|
|
485
|
+
expect(resolveEnvVar('NEXT_PUBLIC_AUTH_URL', context)).toBe(
|
|
486
|
+
'https://auth.example.com',
|
|
487
|
+
);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it('should resolve NEXT_PUBLIC_ prefix for any dependency', () => {
|
|
491
|
+
const context = createContext({
|
|
492
|
+
dependencyUrls: {
|
|
493
|
+
payments: 'https://payments.example.com',
|
|
494
|
+
notifications: 'https://notifications.example.com',
|
|
495
|
+
},
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
expect(resolveEnvVar('NEXT_PUBLIC_PAYMENTS_URL', context)).toBe(
|
|
499
|
+
'https://payments.example.com',
|
|
500
|
+
);
|
|
501
|
+
expect(resolveEnvVar('NEXT_PUBLIC_NOTIFICATIONS_URL', context)).toBe(
|
|
502
|
+
'https://notifications.example.com',
|
|
503
|
+
);
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
it('should return undefined for missing NEXT_PUBLIC_ dependency URL', () => {
|
|
507
|
+
const context = createContext({
|
|
508
|
+
dependencyUrls: { auth: 'https://auth.example.com' },
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
expect(resolveEnvVar('NEXT_PUBLIC_API_URL', context)).toBeUndefined();
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
it('should return undefined when dependencyUrls is not provided', () => {
|
|
515
|
+
const context = createContext();
|
|
516
|
+
|
|
517
|
+
expect(resolveEnvVar('NEXT_PUBLIC_AUTH_URL', context)).toBeUndefined();
|
|
518
|
+
});
|
|
519
|
+
});
|
|
455
520
|
});
|
|
456
521
|
});
|
|
457
522
|
|
|
@@ -634,3 +699,177 @@ describe('validateEnvVars', () => {
|
|
|
634
699
|
});
|
|
635
700
|
});
|
|
636
701
|
});
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Tests for Docker build arg extraction logic.
|
|
705
|
+
* This simulates the behavior in deploy/index.ts where NEXT_PUBLIC_* vars
|
|
706
|
+
* are extracted from resolved vars for Docker build args.
|
|
707
|
+
*/
|
|
708
|
+
describe('Docker build arg extraction', () => {
|
|
709
|
+
const createContext = (
|
|
710
|
+
overrides: Partial<EnvResolverContext> = {},
|
|
711
|
+
): EnvResolverContext => ({
|
|
712
|
+
app: {
|
|
713
|
+
type: 'frontend',
|
|
714
|
+
path: 'apps/web',
|
|
715
|
+
port: 3001,
|
|
716
|
+
dependencies: ['api', 'auth'],
|
|
717
|
+
resolvedDeployTarget: 'dokploy',
|
|
718
|
+
},
|
|
719
|
+
appName: 'web',
|
|
720
|
+
stage: 'production',
|
|
721
|
+
state: createEmptyState('production', 'proj_test', 'env-123'),
|
|
722
|
+
appHostname: 'web.example.com',
|
|
723
|
+
frontendUrls: [],
|
|
724
|
+
...overrides,
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Simulates the build arg extraction logic from deploy/index.ts
|
|
729
|
+
*/
|
|
730
|
+
function extractBuildArgs(resolved: Record<string, string>): {
|
|
731
|
+
buildArgs: string[];
|
|
732
|
+
publicUrlArgNames: string[];
|
|
733
|
+
} {
|
|
734
|
+
const buildArgs: string[] = [];
|
|
735
|
+
const publicUrlArgNames: string[] = [];
|
|
736
|
+
|
|
737
|
+
for (const [key, value] of Object.entries(resolved)) {
|
|
738
|
+
if (key.startsWith('NEXT_PUBLIC_')) {
|
|
739
|
+
buildArgs.push(`${key}=${value}`);
|
|
740
|
+
publicUrlArgNames.push(key);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
return { buildArgs, publicUrlArgNames };
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
it('should extract NEXT_PUBLIC_* vars as build args', () => {
|
|
748
|
+
const context = createContext({
|
|
749
|
+
dependencyUrls: {
|
|
750
|
+
api: 'https://api.example.com',
|
|
751
|
+
auth: 'https://auth.example.com',
|
|
752
|
+
},
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
const sniffedVars = [
|
|
756
|
+
'NEXT_PUBLIC_API_URL',
|
|
757
|
+
'NEXT_PUBLIC_AUTH_URL',
|
|
758
|
+
'NEXT_PUBLIC_STRIPE_KEY',
|
|
759
|
+
];
|
|
760
|
+
|
|
761
|
+
const { resolved } = validateEnvVars(sniffedVars, context);
|
|
762
|
+
|
|
763
|
+
// Simulate user secrets providing STRIPE_KEY
|
|
764
|
+
resolved.NEXT_PUBLIC_STRIPE_KEY = 'pk_test_123';
|
|
765
|
+
|
|
766
|
+
const { buildArgs, publicUrlArgNames } = extractBuildArgs(resolved);
|
|
767
|
+
|
|
768
|
+
expect(publicUrlArgNames).toEqual([
|
|
769
|
+
'NEXT_PUBLIC_API_URL',
|
|
770
|
+
'NEXT_PUBLIC_AUTH_URL',
|
|
771
|
+
'NEXT_PUBLIC_STRIPE_KEY',
|
|
772
|
+
]);
|
|
773
|
+
expect(buildArgs).toEqual([
|
|
774
|
+
'NEXT_PUBLIC_API_URL=https://api.example.com',
|
|
775
|
+
'NEXT_PUBLIC_AUTH_URL=https://auth.example.com',
|
|
776
|
+
'NEXT_PUBLIC_STRIPE_KEY=pk_test_123',
|
|
777
|
+
]);
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
it('should NOT include server-only vars in build args', () => {
|
|
781
|
+
const context = createContext({
|
|
782
|
+
dependencyUrls: { api: 'https://api.example.com' },
|
|
783
|
+
appCredentials: { dbUser: 'web', dbPassword: 'pass' },
|
|
784
|
+
postgres: { host: 'postgres', port: 5432, database: 'mydb' },
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
const sniffedVars = [
|
|
788
|
+
'NEXT_PUBLIC_API_URL',
|
|
789
|
+
'DATABASE_URL',
|
|
790
|
+
'STRIPE_SECRET_KEY',
|
|
791
|
+
];
|
|
792
|
+
|
|
793
|
+
const { resolved } = validateEnvVars(sniffedVars, context);
|
|
794
|
+
|
|
795
|
+
// Add server-only secret
|
|
796
|
+
resolved.STRIPE_SECRET_KEY = 'sk_test_secret';
|
|
797
|
+
|
|
798
|
+
const { buildArgs, publicUrlArgNames } = extractBuildArgs(resolved);
|
|
799
|
+
|
|
800
|
+
// Only NEXT_PUBLIC_* should be in build args
|
|
801
|
+
expect(publicUrlArgNames).toEqual(['NEXT_PUBLIC_API_URL']);
|
|
802
|
+
expect(buildArgs).toEqual(['NEXT_PUBLIC_API_URL=https://api.example.com']);
|
|
803
|
+
|
|
804
|
+
// Server vars should still be in resolved (for runtime)
|
|
805
|
+
expect(resolved.DATABASE_URL).toBe(
|
|
806
|
+
'postgresql://web:pass@postgres:5432/mydb',
|
|
807
|
+
);
|
|
808
|
+
expect(resolved.STRIPE_SECRET_KEY).toBe('sk_test_secret');
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
it('should handle mixed frontend vars correctly', () => {
|
|
812
|
+
const context = createContext({
|
|
813
|
+
dependencyUrls: {
|
|
814
|
+
api: 'https://api.example.com',
|
|
815
|
+
auth: 'https://auth.example.com',
|
|
816
|
+
},
|
|
817
|
+
userSecrets: {
|
|
818
|
+
stage: 'production',
|
|
819
|
+
createdAt: '2024-01-01T00:00:00Z',
|
|
820
|
+
updatedAt: '2024-01-01T00:00:00Z',
|
|
821
|
+
custom: {
|
|
822
|
+
NEXT_PUBLIC_POSTHOG_KEY: 'phc_test123',
|
|
823
|
+
STRIPE_SECRET_KEY: 'sk_test_secret',
|
|
824
|
+
},
|
|
825
|
+
urls: {},
|
|
826
|
+
services: {},
|
|
827
|
+
},
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
const sniffedVars = [
|
|
831
|
+
// From dependencies (auto-generated)
|
|
832
|
+
'NEXT_PUBLIC_API_URL',
|
|
833
|
+
'NEXT_PUBLIC_AUTH_URL',
|
|
834
|
+
// From client config
|
|
835
|
+
'NEXT_PUBLIC_POSTHOG_KEY',
|
|
836
|
+
// From server config
|
|
837
|
+
'STRIPE_SECRET_KEY',
|
|
838
|
+
'DATABASE_URL',
|
|
839
|
+
];
|
|
840
|
+
|
|
841
|
+
const { resolved, missing } = validateEnvVars(sniffedVars, context);
|
|
842
|
+
|
|
843
|
+
// DATABASE_URL is missing (no postgres config)
|
|
844
|
+
expect(missing).toContain('DATABASE_URL');
|
|
845
|
+
|
|
846
|
+
const { publicUrlArgNames } = extractBuildArgs(resolved);
|
|
847
|
+
|
|
848
|
+
// Only NEXT_PUBLIC_* should be build args
|
|
849
|
+
expect(publicUrlArgNames).toHaveLength(3);
|
|
850
|
+
expect(publicUrlArgNames).toContain('NEXT_PUBLIC_API_URL');
|
|
851
|
+
expect(publicUrlArgNames).toContain('NEXT_PUBLIC_AUTH_URL');
|
|
852
|
+
expect(publicUrlArgNames).toContain('NEXT_PUBLIC_POSTHOG_KEY');
|
|
853
|
+
|
|
854
|
+
// Server secret should NOT be in build args
|
|
855
|
+
expect(publicUrlArgNames).not.toContain('STRIPE_SECRET_KEY');
|
|
856
|
+
|
|
857
|
+
// But should be in resolved for runtime
|
|
858
|
+
expect(resolved.STRIPE_SECRET_KEY).toBe('sk_test_secret');
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
it('should return empty build args when no NEXT_PUBLIC_* vars', () => {
|
|
862
|
+
const context = createContext({
|
|
863
|
+
appCredentials: { dbUser: 'web', dbPassword: 'pass' },
|
|
864
|
+
postgres: { host: 'postgres', port: 5432, database: 'mydb' },
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
const sniffedVars = ['DATABASE_URL', 'PORT'];
|
|
868
|
+
|
|
869
|
+
const { resolved } = validateEnvVars(sniffedVars, context);
|
|
870
|
+
const { buildArgs, publicUrlArgNames } = extractBuildArgs(resolved);
|
|
871
|
+
|
|
872
|
+
expect(buildArgs).toEqual([]);
|
|
873
|
+
expect(publicUrlArgNames).toEqual([]);
|
|
874
|
+
});
|
|
875
|
+
});
|
|
@@ -31,8 +31,8 @@ describe('sniffAppEnvironment', () => {
|
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
describe('frontend apps', () => {
|
|
34
|
-
it('should return empty env vars for frontend apps', async () => {
|
|
35
|
-
const app = createApp({ type: 'frontend' });
|
|
34
|
+
it('should return empty env vars for frontend apps with no dependencies', async () => {
|
|
35
|
+
const app = createApp({ type: 'frontend', dependencies: [] });
|
|
36
36
|
|
|
37
37
|
const result = await sniffAppEnvironment(app, 'web', workspacePath);
|
|
38
38
|
|
|
@@ -40,51 +40,117 @@ describe('sniffAppEnvironment', () => {
|
|
|
40
40
|
expect(result.requiredEnvVars).toEqual([]);
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
-
it('should
|
|
43
|
+
it('should return NEXT_PUBLIC_{DEP}_URL for frontend dependencies', async () => {
|
|
44
44
|
const app = createApp({
|
|
45
45
|
type: 'frontend',
|
|
46
|
-
|
|
46
|
+
dependencies: ['api', 'auth'],
|
|
47
47
|
});
|
|
48
48
|
|
|
49
49
|
const result = await sniffAppEnvironment(app, 'web', workspacePath);
|
|
50
50
|
|
|
51
|
-
expect(result.
|
|
51
|
+
expect(result.appName).toBe('web');
|
|
52
|
+
expect(result.requiredEnvVars).toContain('NEXT_PUBLIC_API_URL');
|
|
53
|
+
expect(result.requiredEnvVars).toContain('NEXT_PUBLIC_AUTH_URL');
|
|
54
|
+
expect(result.requiredEnvVars).toHaveLength(2);
|
|
52
55
|
});
|
|
53
|
-
});
|
|
54
56
|
|
|
55
|
-
|
|
56
|
-
it('should return requiredEnv list for entry-based apps', async () => {
|
|
57
|
+
it('should generate uppercase dep names in NEXT_PUBLIC_{DEP}_URL', async () => {
|
|
57
58
|
const app = createApp({
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
type: 'frontend',
|
|
60
|
+
dependencies: ['payments-service', 'notification_api'],
|
|
60
61
|
});
|
|
61
62
|
|
|
62
|
-
const result = await sniffAppEnvironment(app, '
|
|
63
|
+
const result = await sniffAppEnvironment(app, 'web', workspacePath);
|
|
63
64
|
|
|
64
|
-
expect(result.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
expect(result.requiredEnvVars).toContain(
|
|
66
|
+
'NEXT_PUBLIC_PAYMENTS-SERVICE_URL',
|
|
67
|
+
);
|
|
68
|
+
expect(result.requiredEnvVars).toContain(
|
|
69
|
+
'NEXT_PUBLIC_NOTIFICATION_API_URL',
|
|
70
|
+
);
|
|
69
71
|
});
|
|
70
72
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
73
|
+
describe('config sniffing', () => {
|
|
74
|
+
it('should sniff env vars from config.client path', async () => {
|
|
75
|
+
const app = createApp({
|
|
76
|
+
type: 'frontend',
|
|
77
|
+
path: fixturesPath,
|
|
78
|
+
dependencies: [],
|
|
79
|
+
config: {
|
|
80
|
+
client: './simple-entry.ts',
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const result = await sniffAppEnvironment(app, 'web', fixturesPath);
|
|
85
|
+
|
|
86
|
+
expect(result.requiredEnvVars).toContain('PORT');
|
|
87
|
+
expect(result.requiredEnvVars).toContain('DATABASE_URL');
|
|
88
|
+
expect(result.requiredEnvVars).toContain('REDIS_URL');
|
|
89
|
+
});
|
|
81
90
|
|
|
82
|
-
|
|
83
|
-
|
|
91
|
+
it('should sniff env vars from config.server path', async () => {
|
|
92
|
+
const app = createApp({
|
|
93
|
+
type: 'frontend',
|
|
94
|
+
path: fixturesPath,
|
|
95
|
+
dependencies: [],
|
|
96
|
+
config: {
|
|
97
|
+
server: './nested-config-entry.ts',
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const result = await sniffAppEnvironment(app, 'web', fixturesPath);
|
|
102
|
+
|
|
103
|
+
expect(result.requiredEnvVars).toContain('PORT');
|
|
104
|
+
expect(result.requiredEnvVars).toContain('HOST');
|
|
105
|
+
expect(result.requiredEnvVars).toContain('DATABASE_URL');
|
|
106
|
+
});
|
|
84
107
|
|
|
85
|
-
|
|
108
|
+
it('should combine vars from both config.client and config.server', async () => {
|
|
109
|
+
const app = createApp({
|
|
110
|
+
type: 'frontend',
|
|
111
|
+
path: fixturesPath,
|
|
112
|
+
dependencies: ['api'],
|
|
113
|
+
config: {
|
|
114
|
+
client: './simple-entry.ts',
|
|
115
|
+
server: './nested-config-entry.ts',
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const result = await sniffAppEnvironment(app, 'web', fixturesPath);
|
|
120
|
+
|
|
121
|
+
// Dependency var
|
|
122
|
+
expect(result.requiredEnvVars).toContain('NEXT_PUBLIC_API_URL');
|
|
123
|
+
// From simple-entry.ts
|
|
124
|
+
expect(result.requiredEnvVars).toContain('REDIS_URL');
|
|
125
|
+
// From nested-config-entry.ts
|
|
126
|
+
expect(result.requiredEnvVars).toContain('HOST');
|
|
127
|
+
expect(result.requiredEnvVars).toContain('BETTER_AUTH_SECRET');
|
|
128
|
+
});
|
|
86
129
|
|
|
87
|
-
|
|
130
|
+
it('should deduplicate vars from both config files', async () => {
|
|
131
|
+
const app = createApp({
|
|
132
|
+
type: 'frontend',
|
|
133
|
+
path: fixturesPath,
|
|
134
|
+
dependencies: [],
|
|
135
|
+
config: {
|
|
136
|
+
client: './simple-entry.ts',
|
|
137
|
+
server: './nested-config-entry.ts',
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const result = await sniffAppEnvironment(app, 'web', fixturesPath);
|
|
142
|
+
|
|
143
|
+
// Both files have PORT and DATABASE_URL, should only appear once
|
|
144
|
+
const portCount = result.requiredEnvVars.filter(
|
|
145
|
+
(v) => v === 'PORT',
|
|
146
|
+
).length;
|
|
147
|
+
const dbUrlCount = result.requiredEnvVars.filter(
|
|
148
|
+
(v) => v === 'DATABASE_URL',
|
|
149
|
+
).length;
|
|
150
|
+
|
|
151
|
+
expect(portCount).toBe(1);
|
|
152
|
+
expect(dbUrlCount).toBe(1);
|
|
153
|
+
});
|
|
88
154
|
});
|
|
89
155
|
});
|
|
90
156
|
|
|
@@ -119,9 +185,9 @@ describe('sniffAppEnvironment', () => {
|
|
|
119
185
|
});
|
|
120
186
|
|
|
121
187
|
describe('apps without env detection', () => {
|
|
122
|
-
it('should return empty when no envParser or
|
|
188
|
+
it('should return empty when no envParser, entry, or routes', async () => {
|
|
123
189
|
const app = createApp({
|
|
124
|
-
// No envParser or
|
|
190
|
+
// No envParser, entry, or routes
|
|
125
191
|
});
|
|
126
192
|
|
|
127
193
|
const result = await sniffAppEnvironment(app, 'api', workspacePath);
|
|
@@ -142,7 +208,7 @@ describe('sniffAllApps', () => {
|
|
|
142
208
|
port: 3000,
|
|
143
209
|
dependencies: [],
|
|
144
210
|
resolvedDeployTarget: 'dokploy',
|
|
145
|
-
|
|
211
|
+
// No entry, routes, or envParser - will return empty
|
|
146
212
|
},
|
|
147
213
|
auth: {
|
|
148
214
|
type: 'backend',
|
|
@@ -150,7 +216,7 @@ describe('sniffAllApps', () => {
|
|
|
150
216
|
port: 3002,
|
|
151
217
|
dependencies: [],
|
|
152
218
|
resolvedDeployTarget: 'dokploy',
|
|
153
|
-
|
|
219
|
+
// No entry, routes, or envParser - will return empty
|
|
154
220
|
},
|
|
155
221
|
web: {
|
|
156
222
|
type: 'frontend',
|
|
@@ -167,17 +233,17 @@ describe('sniffAllApps', () => {
|
|
|
167
233
|
|
|
168
234
|
expect(results.get('api')).toEqual({
|
|
169
235
|
appName: 'api',
|
|
170
|
-
requiredEnvVars: [
|
|
236
|
+
requiredEnvVars: [],
|
|
171
237
|
});
|
|
172
238
|
|
|
173
239
|
expect(results.get('auth')).toEqual({
|
|
174
240
|
appName: 'auth',
|
|
175
|
-
requiredEnvVars: [
|
|
241
|
+
requiredEnvVars: [],
|
|
176
242
|
});
|
|
177
243
|
|
|
178
244
|
expect(results.get('web')).toEqual({
|
|
179
245
|
appName: 'web',
|
|
180
|
-
requiredEnvVars: [],
|
|
246
|
+
requiredEnvVars: ['NEXT_PUBLIC_API_URL', 'NEXT_PUBLIC_AUTH_URL'],
|
|
181
247
|
});
|
|
182
248
|
});
|
|
183
249
|
|
|
@@ -324,7 +390,7 @@ describe('entry app sniffing via subprocess', () => {
|
|
|
324
390
|
describe('sniffAppEnvironment with entry apps', () => {
|
|
325
391
|
// Integration tests for sniffAppEnvironment with entry-based apps
|
|
326
392
|
|
|
327
|
-
it('should use subprocess sniffing for entry apps
|
|
393
|
+
it('should use subprocess sniffing for entry apps', async () => {
|
|
328
394
|
const app: NormalizedAppConfig = {
|
|
329
395
|
type: 'backend',
|
|
330
396
|
path: fixturesPath,
|
|
@@ -342,25 +408,6 @@ describe('sniffAppEnvironment with entry apps', () => {
|
|
|
342
408
|
expect(result.requiredEnvVars).toContain('REDIS_URL');
|
|
343
409
|
});
|
|
344
410
|
|
|
345
|
-
it('should prefer requiredEnv over sniffing for entry apps', async () => {
|
|
346
|
-
const app: NormalizedAppConfig = {
|
|
347
|
-
type: 'backend',
|
|
348
|
-
path: fixturesPath,
|
|
349
|
-
port: 3000,
|
|
350
|
-
dependencies: [],
|
|
351
|
-
resolvedDeployTarget: 'dokploy',
|
|
352
|
-
entry: './simple-entry.ts',
|
|
353
|
-
requiredEnv: ['CUSTOM_VAR', 'ANOTHER_VAR'], // Should use this instead of sniffing
|
|
354
|
-
};
|
|
355
|
-
|
|
356
|
-
const result = await sniffAppEnvironment(app, 'api', fixturesPath);
|
|
357
|
-
|
|
358
|
-
expect(result.requiredEnvVars).toEqual(['CUSTOM_VAR', 'ANOTHER_VAR']);
|
|
359
|
-
// Should NOT contain the sniffed vars since requiredEnv takes precedence
|
|
360
|
-
expect(result.requiredEnvVars).not.toContain('PORT');
|
|
361
|
-
expect(result.requiredEnvVars).not.toContain('DATABASE_URL');
|
|
362
|
-
});
|
|
363
|
-
|
|
364
411
|
it('should handle entry app that throws and still return captured env vars', async () => {
|
|
365
412
|
const app: NormalizedAppConfig = {
|
|
366
413
|
type: 'backend',
|
|
@@ -499,24 +546,6 @@ describe('sniffAppEnvironment with envParser apps', () => {
|
|
|
499
546
|
expect(result.requiredEnvVars).toContain('DB_POOL_SIZE');
|
|
500
547
|
});
|
|
501
548
|
|
|
502
|
-
it('should prefer requiredEnv over envParser sniffing', async () => {
|
|
503
|
-
const app: NormalizedAppConfig = {
|
|
504
|
-
type: 'backend',
|
|
505
|
-
path: envParserFixturesPath,
|
|
506
|
-
port: 3000,
|
|
507
|
-
dependencies: [],
|
|
508
|
-
resolvedDeployTarget: 'dokploy',
|
|
509
|
-
envParser: './valid-env-parser.ts#envParser',
|
|
510
|
-
requiredEnv: ['CUSTOM_VAR'], // Should use this instead
|
|
511
|
-
};
|
|
512
|
-
|
|
513
|
-
const result = await sniffAppEnvironment(app, 'api', envParserFixturesPath);
|
|
514
|
-
|
|
515
|
-
expect(result.requiredEnvVars).toEqual(['CUSTOM_VAR']);
|
|
516
|
-
// Should NOT contain the sniffed vars
|
|
517
|
-
expect(result.requiredEnvVars).not.toContain('PORT');
|
|
518
|
-
});
|
|
519
|
-
|
|
520
549
|
it('should handle envParser that exports non-function gracefully', async () => {
|
|
521
550
|
const app: NormalizedAppConfig = {
|
|
522
551
|
type: 'backend',
|
|
@@ -663,24 +692,6 @@ describe('sniffAppEnvironment with route-based apps', () => {
|
|
|
663
692
|
expect(result.requiredEnvVars).toContain('AUTH_URL');
|
|
664
693
|
});
|
|
665
694
|
|
|
666
|
-
it('should prefer requiredEnv over route sniffing', async () => {
|
|
667
|
-
const app: NormalizedAppConfig = {
|
|
668
|
-
type: 'backend',
|
|
669
|
-
path: routeAppsFixturesPath,
|
|
670
|
-
port: 3000,
|
|
671
|
-
dependencies: [],
|
|
672
|
-
resolvedDeployTarget: 'dokploy',
|
|
673
|
-
routes: './endpoints/**/*.ts',
|
|
674
|
-
requiredEnv: ['CUSTOM_VAR'], // Should use this instead
|
|
675
|
-
};
|
|
676
|
-
|
|
677
|
-
const result = await sniffAppEnvironment(app, 'api', routeAppsFixturesPath);
|
|
678
|
-
|
|
679
|
-
expect(result.requiredEnvVars).toEqual(['CUSTOM_VAR']);
|
|
680
|
-
// Should NOT contain the sniffed vars
|
|
681
|
-
expect(result.requiredEnvVars).not.toContain('DATABASE_URL');
|
|
682
|
-
});
|
|
683
|
-
|
|
684
695
|
it('should handle route pattern that matches no files', async () => {
|
|
685
696
|
const app: NormalizedAppConfig = {
|
|
686
697
|
type: 'backend',
|