revisium 2.3.0 → 2.5.0-alpha.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/.github/workflows/build.yml +11 -30
- package/.github/workflows/ci.yml +0 -6
- package/.github/workflows/npm-publish.yml +13 -78
- package/.github/workflows/release-train.yml +90 -0
- package/AGENTS.md +98 -0
- package/README.md +31 -13
- package/dist/e2e/utils/cli-runner.d.ts +2 -0
- package/dist/e2e/utils/cli-runner.js +14 -7
- package/dist/e2e/utils/cli-runner.js.map +1 -1
- package/dist/e2e/utils/matrix-fixtures.d.ts +26 -0
- package/dist/e2e/utils/matrix-fixtures.js +109 -0
- package/dist/e2e/utils/matrix-fixtures.js.map +1 -0
- package/dist/e2e/utils/matrix-workspace.d.ts +25 -0
- package/dist/e2e/utils/matrix-workspace.js +61 -0
- package/dist/e2e/utils/matrix-workspace.js.map +1 -0
- package/dist/e2e/utils/standalone-api.d.ts +53 -0
- package/dist/e2e/utils/standalone-api.js +146 -0
- package/dist/e2e/utils/standalone-api.js.map +1 -0
- package/dist/e2e/utils/standalone-runner.d.ts +23 -0
- package/dist/e2e/utils/standalone-runner.js +182 -0
- package/dist/e2e/utils/standalone-runner.js.map +1 -0
- package/dist/package.json +6 -3
- package/dist/src/app.module.js +51 -0
- package/dist/src/app.module.js.map +1 -1
- package/dist/src/commands/auth/auth-command.utils.d.ts +8 -0
- package/dist/src/commands/auth/auth-command.utils.js +28 -0
- package/dist/src/commands/auth/auth-command.utils.js.map +1 -0
- package/dist/src/commands/auth/auth-login.command.d.ts +29 -0
- package/dist/src/commands/auth/auth-login.command.js +155 -0
- package/dist/src/commands/auth/auth-login.command.js.map +1 -0
- package/dist/src/commands/auth/auth-logout.command.d.ts +19 -0
- package/dist/src/commands/auth/auth-logout.command.js +86 -0
- package/dist/src/commands/auth/auth-logout.command.js.map +1 -0
- package/dist/src/commands/auth/auth-status.command.d.ts +19 -0
- package/dist/src/commands/auth/auth-status.command.js +101 -0
- package/dist/src/commands/auth/auth-status.command.js.map +1 -0
- package/dist/src/commands/auth/auth.command.d.ts +5 -0
- package/dist/src/commands/auth/auth.command.js +35 -0
- package/dist/src/commands/auth/auth.command.js.map +1 -0
- package/dist/src/commands/base-sync.command.d.ts +2 -2
- package/dist/src/commands/base-sync.command.js +1 -2
- package/dist/src/commands/base-sync.command.js.map +1 -1
- package/dist/src/commands/base.command.d.ts +2 -0
- package/dist/src/commands/base.command.js +13 -0
- package/dist/src/commands/base.command.js.map +1 -1
- package/dist/src/commands/context/context-create.command.d.ts +28 -0
- package/dist/src/commands/context/context-create.command.js +172 -0
- package/dist/src/commands/context/context-create.command.js.map +1 -0
- package/dist/src/commands/context/context-list.command.d.ts +9 -0
- package/dist/src/commands/context/context-list.command.js +46 -0
- package/dist/src/commands/context/context-list.command.js.map +1 -0
- package/dist/src/commands/context/context-remove.command.d.ts +9 -0
- package/dist/src/commands/context/context-remove.command.js +48 -0
- package/dist/src/commands/context/context-remove.command.js.map +1 -0
- package/dist/src/commands/context/context-show.command.d.ts +9 -0
- package/dist/src/commands/context/context-show.command.js +64 -0
- package/dist/src/commands/context/context-show.command.js.map +1 -0
- package/dist/src/commands/context/context-use.command.d.ts +9 -0
- package/dist/src/commands/context/context-use.command.js +45 -0
- package/dist/src/commands/context/context-use.command.js.map +1 -0
- package/dist/src/commands/context/context.command.d.ts +5 -0
- package/dist/src/commands/context/context.command.js +43 -0
- package/dist/src/commands/context/context.command.js.map +1 -0
- package/dist/src/commands/endpoint/endpoint-ensure.command.d.ts +18 -0
- package/dist/src/commands/endpoint/endpoint-ensure.command.js +90 -0
- package/dist/src/commands/endpoint/endpoint-ensure.command.js.map +1 -0
- package/dist/src/commands/endpoint/endpoint-list.command.d.ts +14 -0
- package/dist/src/commands/endpoint/endpoint-list.command.js +62 -0
- package/dist/src/commands/endpoint/endpoint-list.command.js.map +1 -0
- package/dist/src/commands/endpoint/endpoint.command.d.ts +5 -0
- package/dist/src/commands/endpoint/endpoint.command.js +34 -0
- package/dist/src/commands/endpoint/endpoint.command.js.map +1 -0
- package/dist/src/commands/example/example-bootstrap.command.d.ts +24 -0
- package/dist/src/commands/example/example-bootstrap.command.js +133 -0
- package/dist/src/commands/example/example-bootstrap.command.js.map +1 -0
- package/dist/src/commands/example/example.command.d.ts +5 -0
- package/dist/src/commands/example/example.command.js +33 -0
- package/dist/src/commands/example/example.command.js.map +1 -0
- package/dist/src/commands/instance/instance-add.command.d.ts +16 -0
- package/dist/src/commands/instance/instance-add.command.js +83 -0
- package/dist/src/commands/instance/instance-add.command.js.map +1 -0
- package/dist/src/commands/instance/instance-list.command.d.ts +9 -0
- package/dist/src/commands/instance/instance-list.command.js +45 -0
- package/dist/src/commands/instance/instance-list.command.js.map +1 -0
- package/dist/src/commands/instance/instance-remove.command.d.ts +9 -0
- package/dist/src/commands/instance/instance-remove.command.js +54 -0
- package/dist/src/commands/instance/instance-remove.command.js.map +1 -0
- package/dist/src/commands/instance/instance-show.command.d.ts +9 -0
- package/dist/src/commands/instance/instance-show.command.js +50 -0
- package/dist/src/commands/instance/instance-show.command.js.map +1 -0
- package/dist/src/commands/instance/instance.command.d.ts +5 -0
- package/dist/src/commands/instance/instance.command.js +41 -0
- package/dist/src/commands/instance/instance.command.js.map +1 -0
- package/dist/src/commands/migration/apply-migrations.command.js +1 -0
- package/dist/src/commands/migration/apply-migrations.command.js.map +1 -1
- package/dist/src/commands/project/project-ensure.command.d.ts +16 -0
- package/dist/src/commands/project/project-ensure.command.js +71 -0
- package/dist/src/commands/project/project-ensure.command.js.map +1 -0
- package/dist/src/commands/project/project.command.d.ts +5 -0
- package/dist/src/commands/project/project.command.js +33 -0
- package/dist/src/commands/project/project.command.js.map +1 -0
- package/dist/src/services/bootstrap/bootstrap.service.d.ts +112 -0
- package/dist/src/services/bootstrap/bootstrap.service.js +438 -0
- package/dist/src/services/bootstrap/bootstrap.service.js.map +1 -0
- package/dist/src/services/bootstrap/index.d.ts +1 -0
- package/dist/src/services/bootstrap/index.js +6 -0
- package/dist/src/services/bootstrap/index.js.map +1 -0
- package/dist/src/services/connection/api-client.d.ts +4 -0
- package/dist/src/services/connection/api-client.js +67 -5
- package/dist/src/services/connection/api-client.js.map +1 -1
- package/dist/src/services/connection/connection-factory.service.js +4 -3
- package/dist/src/services/connection/connection-factory.service.js.map +1 -1
- package/dist/src/services/connection/connection.service.d.ts +10 -2
- package/dist/src/services/connection/connection.service.js +49 -4
- package/dist/src/services/connection/connection.service.js.map +1 -1
- package/dist/src/services/credentials/credential-store.service.d.ts +19 -0
- package/dist/src/services/credentials/credential-store.service.js +112 -0
- package/dist/src/services/credentials/credential-store.service.js.map +1 -0
- package/dist/src/services/credentials/credential-target.service.d.ts +22 -0
- package/dist/src/services/credentials/credential-target.service.js +107 -0
- package/dist/src/services/credentials/credential-target.service.js.map +1 -0
- package/dist/src/services/credentials/index.d.ts +2 -0
- package/dist/src/services/credentials/index.js +8 -0
- package/dist/src/services/credentials/index.js.map +1 -0
- package/dist/src/services/index.d.ts +3 -0
- package/dist/src/services/index.js +3 -0
- package/dist/src/services/index.js.map +1 -1
- package/dist/src/services/url/auth-prompt.service.d.ts +3 -1
- package/dist/src/services/url/auth-prompt.service.js +3 -0
- package/dist/src/services/url/auth-prompt.service.js.map +1 -1
- package/dist/src/services/url/url-builder.service.js +3 -0
- package/dist/src/services/url/url-builder.service.js.map +1 -1
- package/dist/src/services/url/url-parser.service.d.ts +1 -1
- package/dist/src/services/url/url-parser.service.js +12 -6
- package/dist/src/services/url/url-parser.service.js.map +1 -1
- package/dist/src/services/workspace/index.d.ts +1 -0
- package/dist/src/services/workspace/index.js +11 -0
- package/dist/src/services/workspace/index.js.map +1 -0
- package/dist/src/services/workspace/workspace-config.service.d.ts +66 -0
- package/dist/src/services/workspace/workspace-config.service.js +313 -0
- package/dist/src/services/workspace/workspace-config.service.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/docs/authentication.md +69 -26
- package/docs/bootstrap-commands.md +105 -0
- package/docs/configuration.md +49 -22
- package/docs/url-format.md +27 -2
- package/docs/workspace-config.md +135 -0
- package/e2e/jest-matrix.json +14 -0
- package/e2e/matrix/M01-auth-commands.e2e-spec.ts +241 -0
- package/e2e/matrix/M02-instance-commands.e2e-spec.ts +213 -0
- package/e2e/matrix/M03-context-commands.e2e-spec.ts +279 -0
- package/e2e/matrix/M04-project-ensure.e2e-spec.ts +218 -0
- package/e2e/matrix/M05-endpoint-commands.e2e-spec.ts +172 -0
- package/e2e/matrix/M06-example-bootstrap.e2e-spec.ts +437 -0
- package/e2e/matrix/M07-migrate.e2e-spec.ts +229 -0
- package/e2e/matrix/M08-schema.e2e-spec.ts +163 -0
- package/e2e/matrix/M09-rows.e2e-spec.ts +185 -0
- package/e2e/matrix/M10-sync.e2e-spec.ts +224 -0
- package/e2e/matrix/M11-target-resolution.e2e-spec.ts +177 -0
- package/e2e/matrix/M12-error-paths.e2e-spec.ts +162 -0
- package/e2e/matrix/M13-no-auth-mode.e2e-spec.ts +119 -0
- package/e2e/matrix/M14-multi-instance-workspace.e2e-spec.ts +182 -0
- package/e2e/matrix/README.md +354 -0
- package/e2e/tests/01-auth.e2e-spec.ts +19 -0
- package/e2e/tests/07-workspace-config.e2e-spec.ts +492 -0
- package/e2e/tests/08-bootstrap.e2e-spec.ts +304 -0
- package/e2e/utils/cli-runner.ts +27 -8
- package/e2e/utils/matrix-fixtures.ts +141 -0
- package/e2e/utils/matrix-workspace.ts +106 -0
- package/e2e/utils/standalone-api.ts +314 -0
- package/e2e/utils/standalone-runner.ts +276 -0
- package/package.json +6 -3
- package/src/app.module.ts +54 -0
- package/src/commands/auth/__tests__/auth-command.utils.spec.ts +41 -0
- package/src/commands/auth/__tests__/auth-login.command.spec.ts +131 -0
- package/src/commands/auth/__tests__/auth-logout.command.spec.ts +85 -0
- package/src/commands/auth/__tests__/auth-status.command.spec.ts +106 -0
- package/src/commands/auth/__tests__/auth.command.spec.ts +18 -0
- package/src/commands/auth/auth-command.utils.ts +36 -0
- package/src/commands/auth/auth-login.command.ts +146 -0
- package/src/commands/auth/auth-logout.command.ts +71 -0
- package/src/commands/auth/auth-status.command.ts +89 -0
- package/src/commands/auth/auth.command.ts +20 -0
- package/src/commands/base-sync.command.ts +2 -3
- package/src/commands/base.command.ts +11 -0
- package/src/commands/context/context-create.command.ts +173 -0
- package/src/commands/context/context-list.command.ts +36 -0
- package/src/commands/context/context-remove.command.ts +35 -0
- package/src/commands/context/context-show.command.ts +59 -0
- package/src/commands/context/context-use.command.ts +31 -0
- package/src/commands/context/context.command.ts +28 -0
- package/src/commands/endpoint/__tests__/endpoint-ensure.command.spec.ts +129 -0
- package/src/commands/endpoint/__tests__/endpoint-list.command.spec.ts +59 -0
- package/src/commands/endpoint/__tests__/endpoint.command.spec.ts +14 -0
- package/src/commands/endpoint/endpoint-ensure.command.ts +74 -0
- package/src/commands/endpoint/endpoint-list.command.ts +48 -0
- package/src/commands/endpoint/endpoint.command.ts +19 -0
- package/src/commands/example/__tests__/example-bootstrap.command.spec.ts +145 -0
- package/src/commands/example/__tests__/example.command.spec.ts +14 -0
- package/src/commands/example/example-bootstrap.command.ts +122 -0
- package/src/commands/example/example.command.ts +18 -0
- package/src/commands/instance/instance-add.command.ts +72 -0
- package/src/commands/instance/instance-list.command.ts +31 -0
- package/src/commands/instance/instance-remove.command.ts +44 -0
- package/src/commands/instance/instance-show.command.ts +35 -0
- package/src/commands/instance/instance.command.ts +26 -0
- package/src/commands/migration/apply-migrations.command.ts +1 -0
- package/src/commands/project/__tests__/project-ensure.command.spec.ts +119 -0
- package/src/commands/project/__tests__/project.command.spec.ts +14 -0
- package/src/commands/project/project-ensure.command.ts +61 -0
- package/src/commands/project/project.command.ts +18 -0
- package/src/services/bootstrap/__tests__/bootstrap.service.spec.ts +798 -0
- package/src/services/bootstrap/bootstrap.service.ts +698 -0
- package/src/services/bootstrap/index.ts +12 -0
- package/src/services/connection/__tests__/api-client.spec.ts +262 -0
- package/src/services/connection/__tests__/connection-factory.service.spec.ts +2 -0
- package/src/services/connection/__tests__/connection.service.spec.ts +161 -0
- package/src/services/connection/api-client.ts +88 -5
- package/src/services/connection/connection-factory.service.ts +4 -3
- package/src/services/connection/connection.service.ts +74 -3
- package/src/services/credentials/__tests__/credential-store.service.spec.ts +128 -0
- package/src/services/credentials/__tests__/credential-target.service.spec.ts +126 -0
- package/src/services/credentials/credential-store.service.ts +145 -0
- package/src/services/credentials/credential-target.service.ts +145 -0
- package/src/services/credentials/index.ts +9 -0
- package/src/services/index.ts +3 -0
- package/src/services/url/__tests__/url-builder.service.spec.ts +17 -0
- package/src/services/url/__tests__/url-parser.service.spec.ts +162 -0
- package/src/services/url/auth-prompt.service.ts +6 -1
- package/src/services/url/url-builder.service.ts +4 -0
- package/src/services/url/url-parser.service.ts +21 -6
- package/src/services/workspace/__tests__/workspace-config.service.spec.ts +378 -0
- package/src/services/workspace/index.ts +14 -0
- package/src/services/workspace/workspace-config.service.ts +467 -0
- package/.github/workflows/bump-version.yml +0 -86
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { UrlParserService } from '../url-parser.service';
|
|
2
|
+
|
|
3
|
+
describe('UrlParserService', () => {
|
|
4
|
+
let service: UrlParserService;
|
|
5
|
+
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
service = new UrlParserService();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
describe('parse - revisium+http://', () => {
|
|
11
|
+
it('forces HTTP for non-localhost host', () => {
|
|
12
|
+
const result = service.parse(
|
|
13
|
+
'revisium+http://admin:pass@my-service:80/org/proj/branch',
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
expect(result).toEqual({
|
|
17
|
+
baseUrl: 'http://my-service:80',
|
|
18
|
+
username: 'admin',
|
|
19
|
+
password: 'pass',
|
|
20
|
+
token: undefined,
|
|
21
|
+
apikey: undefined,
|
|
22
|
+
organization: 'org',
|
|
23
|
+
project: 'proj',
|
|
24
|
+
branch: 'branch',
|
|
25
|
+
revision: undefined,
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('forces HTTP for non-localhost host with draft revision', () => {
|
|
30
|
+
const result = service.parse(
|
|
31
|
+
'revisium+http://admin:pass@my-service:80/org/proj/branch:draft',
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
expect(result).toEqual({
|
|
35
|
+
baseUrl: 'http://my-service:80',
|
|
36
|
+
username: 'admin',
|
|
37
|
+
password: 'pass',
|
|
38
|
+
token: undefined,
|
|
39
|
+
apikey: undefined,
|
|
40
|
+
organization: 'org',
|
|
41
|
+
project: 'proj',
|
|
42
|
+
branch: 'branch',
|
|
43
|
+
revision: 'draft',
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('forces HTTP without port', () => {
|
|
48
|
+
const result = service.parse(
|
|
49
|
+
'revisium+http://admin:pass@my-service/org/proj/branch',
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
expect(result).toEqual({
|
|
53
|
+
baseUrl: 'http://my-service',
|
|
54
|
+
username: 'admin',
|
|
55
|
+
password: 'pass',
|
|
56
|
+
token: undefined,
|
|
57
|
+
apikey: undefined,
|
|
58
|
+
organization: 'org',
|
|
59
|
+
project: 'proj',
|
|
60
|
+
branch: 'branch',
|
|
61
|
+
revision: undefined,
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('forces HTTP with token auth', () => {
|
|
66
|
+
const result = service.parse(
|
|
67
|
+
'revisium+http://payment-svc:80/org/proj/master?token=abc123',
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
expect(result).toEqual({
|
|
71
|
+
baseUrl: 'http://payment-svc:80',
|
|
72
|
+
username: undefined,
|
|
73
|
+
password: undefined,
|
|
74
|
+
token: 'abc123',
|
|
75
|
+
apikey: undefined,
|
|
76
|
+
organization: 'org',
|
|
77
|
+
project: 'proj',
|
|
78
|
+
branch: 'master',
|
|
79
|
+
revision: undefined,
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('parse - revisium+https://', () => {
|
|
85
|
+
it('forces HTTPS for any host', () => {
|
|
86
|
+
const result = service.parse(
|
|
87
|
+
'revisium+https://admin:pass@my-service/org/proj/branch',
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
expect(result).toEqual({
|
|
91
|
+
baseUrl: 'https://my-service',
|
|
92
|
+
username: 'admin',
|
|
93
|
+
password: 'pass',
|
|
94
|
+
token: undefined,
|
|
95
|
+
apikey: undefined,
|
|
96
|
+
organization: 'org',
|
|
97
|
+
project: 'proj',
|
|
98
|
+
branch: 'branch',
|
|
99
|
+
revision: undefined,
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('forces HTTPS even for localhost', () => {
|
|
104
|
+
const result = service.parse(
|
|
105
|
+
'revisium+https://admin:pass@localhost:8443/org/proj/main',
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
expect(result).toEqual({
|
|
109
|
+
baseUrl: 'https://localhost:8443',
|
|
110
|
+
username: 'admin',
|
|
111
|
+
password: 'pass',
|
|
112
|
+
token: undefined,
|
|
113
|
+
apikey: undefined,
|
|
114
|
+
organization: 'org',
|
|
115
|
+
project: 'proj',
|
|
116
|
+
branch: 'main',
|
|
117
|
+
revision: undefined,
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('parse - revisium:// (unchanged behavior)', () => {
|
|
123
|
+
it('auto-detects http for localhost', () => {
|
|
124
|
+
const result = service.parse(
|
|
125
|
+
'revisium://admin:pass@localhost:8888/org/proj/branch',
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
expect(result.baseUrl).toBe('http://localhost:8888');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('auto-detects https for remote host', () => {
|
|
132
|
+
const result = service.parse(
|
|
133
|
+
'revisium://admin:pass@cloud.revisium.io/org/proj/branch',
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
expect(result.baseUrl).toBe('https://cloud.revisium.io');
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe('buildBaseUrlFromHost - protocolOverride', () => {
|
|
141
|
+
it('uses override when provided', () => {
|
|
142
|
+
expect(service.buildBaseUrlFromHost('my-service:80', 'http')).toBe(
|
|
143
|
+
'http://my-service:80',
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('override takes precedence over localhost auto-detect', () => {
|
|
148
|
+
expect(service.buildBaseUrlFromHost('localhost:8080', 'https')).toBe(
|
|
149
|
+
'https://localhost:8080',
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('falls back to auto-detect when no override', () => {
|
|
154
|
+
expect(service.buildBaseUrlFromHost('localhost:8080')).toBe(
|
|
155
|
+
'http://localhost:8080',
|
|
156
|
+
);
|
|
157
|
+
expect(service.buildBaseUrlFromHost('cloud.revisium.io')).toBe(
|
|
158
|
+
'https://cloud.revisium.io',
|
|
159
|
+
);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Injectable } from '@nestjs/common';
|
|
2
2
|
import { InteractiveService } from '../common';
|
|
3
3
|
|
|
4
|
-
export type AuthMethod = 'token' | 'apikey' | 'password';
|
|
4
|
+
export type AuthMethod = 'none' | 'token' | 'apikey' | 'password';
|
|
5
5
|
|
|
6
6
|
export type AuthCredentials =
|
|
7
|
+
| { method: 'none' }
|
|
7
8
|
| { method: 'token'; token: string }
|
|
8
9
|
| { method: 'apikey'; apikey: string }
|
|
9
10
|
| { method: 'password'; username: string; password: string };
|
|
@@ -27,6 +28,10 @@ export class AuthPromptService {
|
|
|
27
28
|
],
|
|
28
29
|
);
|
|
29
30
|
|
|
31
|
+
if (authMethod === 'none') {
|
|
32
|
+
return { method: 'none' };
|
|
33
|
+
}
|
|
34
|
+
|
|
30
35
|
if (authMethod === 'token') {
|
|
31
36
|
const token = await this.interactive.promptPassword(
|
|
32
37
|
`[${label}] Paste token:`,
|
|
@@ -46,6 +46,10 @@ export class UrlBuilderService {
|
|
|
46
46
|
|
|
47
47
|
const basePath = `revisium://${baseUrlWithoutProtocol}/${url.organization}/${url.project}${branchPart}`;
|
|
48
48
|
|
|
49
|
+
if (url.auth.method === 'none') {
|
|
50
|
+
return basePath;
|
|
51
|
+
}
|
|
52
|
+
|
|
49
53
|
if (url.auth.method === 'token') {
|
|
50
54
|
const tokenValue = maskSecrets ? '****' : url.auth.token;
|
|
51
55
|
return `${basePath}?token=${tokenValue}`;
|
|
@@ -27,6 +27,14 @@ function validatePort(port: number, context: string): void {
|
|
|
27
27
|
@Injectable()
|
|
28
28
|
export class UrlParserService {
|
|
29
29
|
parse(input: string): RevisiumUrl {
|
|
30
|
+
if (input.startsWith('revisium+http://')) {
|
|
31
|
+
return this.parseRevisiumUrl(input, 'http');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (input.startsWith('revisium+https://')) {
|
|
35
|
+
return this.parseRevisiumUrl(input, 'https');
|
|
36
|
+
}
|
|
37
|
+
|
|
30
38
|
if (input.startsWith('revisium://')) {
|
|
31
39
|
return this.parseRevisiumUrl(input);
|
|
32
40
|
}
|
|
@@ -39,7 +47,10 @@ export class UrlParserService {
|
|
|
39
47
|
return `${protocol}://${host}${portSuffix}`;
|
|
40
48
|
}
|
|
41
49
|
|
|
42
|
-
buildBaseUrlFromHost(
|
|
50
|
+
buildBaseUrlFromHost(
|
|
51
|
+
hostWithPort: string,
|
|
52
|
+
protocolOverride?: 'http' | 'https',
|
|
53
|
+
): string {
|
|
43
54
|
const [host, portStr] = hostWithPort.split(':');
|
|
44
55
|
const port = portStr ? Number.parseInt(portStr, 10) : undefined;
|
|
45
56
|
|
|
@@ -47,8 +58,9 @@ export class UrlParserService {
|
|
|
47
58
|
validatePort(port, `in "${hostWithPort}"`);
|
|
48
59
|
}
|
|
49
60
|
|
|
50
|
-
const
|
|
51
|
-
|
|
61
|
+
const protocol =
|
|
62
|
+
protocolOverride ??
|
|
63
|
+
(LOCALHOST_HOSTS.has(host.toLowerCase()) ? 'http' : 'https');
|
|
52
64
|
|
|
53
65
|
if (port) {
|
|
54
66
|
return this.buildBaseUrl(protocol, host, port);
|
|
@@ -65,8 +77,11 @@ export class UrlParserService {
|
|
|
65
77
|
validatePort(port, context);
|
|
66
78
|
}
|
|
67
79
|
|
|
68
|
-
private parseRevisiumUrl(
|
|
69
|
-
|
|
80
|
+
private parseRevisiumUrl(
|
|
81
|
+
url: string,
|
|
82
|
+
forceProtocol?: 'http' | 'https',
|
|
83
|
+
): RevisiumUrl {
|
|
84
|
+
const withoutProtocol = url.replace(/^revisium(\+https?)?:\/\//, '');
|
|
70
85
|
|
|
71
86
|
const [mainPart, queryString] = withoutProtocol.split('?');
|
|
72
87
|
const queryParams = this.parseQueryParams(queryString);
|
|
@@ -104,7 +119,7 @@ export class UrlParserService {
|
|
|
104
119
|
const pathParts = hostAndPath.split('/');
|
|
105
120
|
const hostWithPort = pathParts[0];
|
|
106
121
|
|
|
107
|
-
const baseUrl = this.buildBaseUrlFromHost(hostWithPort);
|
|
122
|
+
const baseUrl = this.buildBaseUrlFromHost(hostWithPort, forceProtocol);
|
|
108
123
|
|
|
109
124
|
const branchWithRevision = pathParts[3] || undefined;
|
|
110
125
|
let branch: string | undefined;
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { WorkspaceConfigService } from '../workspace-config.service';
|
|
5
|
+
import { UrlParserService } from '../../url/url-parser.service';
|
|
6
|
+
import { CredentialStoreService } from '../../credentials';
|
|
7
|
+
|
|
8
|
+
describe('WorkspaceConfigService', () => {
|
|
9
|
+
let service: WorkspaceConfigService;
|
|
10
|
+
let tempDir: string;
|
|
11
|
+
|
|
12
|
+
beforeEach(async () => {
|
|
13
|
+
service = new WorkspaceConfigService(new UrlParserService());
|
|
14
|
+
tempDir = await mkdtemp(join(tmpdir(), 'revisium-cli-workspace-'));
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(async () => {
|
|
18
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('writes and discovers the nearest workspace config from a child folder', async () => {
|
|
22
|
+
const loaded = await service.loadOrCreate(tempDir);
|
|
23
|
+
loaded.config.instances.local = {
|
|
24
|
+
baseUrl: 'http://localhost:9222',
|
|
25
|
+
authMode: 'none',
|
|
26
|
+
};
|
|
27
|
+
await service.save(loaded.path, loaded.config);
|
|
28
|
+
|
|
29
|
+
const childDir = join(tempDir, 'examples', 'dictionary');
|
|
30
|
+
await mkdir(childDir, { recursive: true });
|
|
31
|
+
|
|
32
|
+
const discovered = await service.load(childDir);
|
|
33
|
+
|
|
34
|
+
expect(discovered?.path).toBe(loaded.path);
|
|
35
|
+
expect(discovered?.config.instances.local).toEqual({
|
|
36
|
+
baseUrl: 'http://localhost:9222',
|
|
37
|
+
authMode: 'none',
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('normalizes revisium URLs to server base URLs', () => {
|
|
42
|
+
expect(
|
|
43
|
+
service.normalizeBaseUrl('revisium://localhost:9222/admin/proj'),
|
|
44
|
+
).toBe('http://localhost:9222');
|
|
45
|
+
expect(service.normalizeBaseUrl('https://cloud.revisium.io/')).toBe(
|
|
46
|
+
'https://cloud.revisium.io',
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('rejects direct http instance URLs with missing host or extra target path', () => {
|
|
51
|
+
expect(() => service.normalizeBaseUrl('https://')).toThrow(
|
|
52
|
+
'Use http(s)://host[:port]',
|
|
53
|
+
);
|
|
54
|
+
expect(() =>
|
|
55
|
+
service.normalizeBaseUrl('https://cloud.revisium.io/admin/dictionary'),
|
|
56
|
+
).toThrow('Use http(s)://host[:port]');
|
|
57
|
+
expect(() =>
|
|
58
|
+
service.normalizeBaseUrl('https://cloud.revisium.io?token=secret'),
|
|
59
|
+
).toThrow('Use http(s)://host[:port]');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('parses context URLs with default branch and revision', () => {
|
|
63
|
+
expect(
|
|
64
|
+
service.parseContextUrl('revisium://localhost:9222/admin/dictionary'),
|
|
65
|
+
).toEqual({
|
|
66
|
+
baseUrl: 'http://localhost:9222',
|
|
67
|
+
organization: 'admin',
|
|
68
|
+
project: 'dictionary',
|
|
69
|
+
branch: 'master',
|
|
70
|
+
revision: 'draft',
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('resolves no-auth workspace contexts', () => {
|
|
75
|
+
const url = service.resolveConnection(
|
|
76
|
+
{
|
|
77
|
+
path: join(tempDir, '.revisium', 'revisium-cli.config.json'),
|
|
78
|
+
config: {
|
|
79
|
+
version: 1,
|
|
80
|
+
currentContext: 'dictionary-local',
|
|
81
|
+
instances: {
|
|
82
|
+
local: {
|
|
83
|
+
baseUrl: 'http://localhost:9222',
|
|
84
|
+
authMode: 'none',
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
contexts: {
|
|
88
|
+
'dictionary-local': {
|
|
89
|
+
instance: 'local',
|
|
90
|
+
organization: 'admin',
|
|
91
|
+
project: 'dictionary',
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
undefined,
|
|
97
|
+
{},
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
expect(url).toEqual({
|
|
101
|
+
baseUrl: 'http://localhost:9222',
|
|
102
|
+
auth: { method: 'none' },
|
|
103
|
+
organization: 'admin',
|
|
104
|
+
project: 'dictionary',
|
|
105
|
+
branch: 'master',
|
|
106
|
+
revision: 'draft',
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('loads an existing context with its config path', async () => {
|
|
111
|
+
const loaded = await service.loadOrCreate(tempDir);
|
|
112
|
+
loaded.config.contexts.demo = {
|
|
113
|
+
instance: 'local',
|
|
114
|
+
organization: 'admin',
|
|
115
|
+
project: 'dictionary',
|
|
116
|
+
};
|
|
117
|
+
await service.save(loaded.path, loaded.config);
|
|
118
|
+
|
|
119
|
+
const result = await service.loadContext('demo', tempDir);
|
|
120
|
+
|
|
121
|
+
expect(result.loaded.path).toBe(loaded.path);
|
|
122
|
+
expect(result.context.project).toBe('dictionary');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('fails when loading a missing context', async () => {
|
|
126
|
+
const loaded = await service.loadOrCreate(tempDir);
|
|
127
|
+
await service.save(loaded.path, loaded.config);
|
|
128
|
+
|
|
129
|
+
await expect(service.loadContext('missing', tempDir)).rejects.toThrow(
|
|
130
|
+
'Revisium context "missing" was not found',
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('lets environment credentials override no-auth mode', () => {
|
|
135
|
+
const url = service.resolveConnection(
|
|
136
|
+
{
|
|
137
|
+
path: join(tempDir, '.revisium', 'revisium-cli.config.json'),
|
|
138
|
+
config: {
|
|
139
|
+
version: 1,
|
|
140
|
+
currentContext: 'dictionary-local',
|
|
141
|
+
instances: {
|
|
142
|
+
local: {
|
|
143
|
+
baseUrl: 'http://localhost:9222',
|
|
144
|
+
authMode: 'none',
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
contexts: {
|
|
148
|
+
'dictionary-local': {
|
|
149
|
+
instance: 'local',
|
|
150
|
+
organization: 'admin',
|
|
151
|
+
project: 'dictionary',
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
undefined,
|
|
157
|
+
{ apikey: 'rev_test' },
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
expect(url.auth).toEqual({ method: 'apikey', apikey: 'rev_test' });
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('resolves saved credentials for stored workspace contexts', () => {
|
|
164
|
+
const credentialStore = {
|
|
165
|
+
getCredential: jest
|
|
166
|
+
.fn()
|
|
167
|
+
.mockReturnValue({ method: 'apikey', apikey: 'rev_saved' }),
|
|
168
|
+
} as unknown as CredentialStoreService;
|
|
169
|
+
service = new WorkspaceConfigService(
|
|
170
|
+
new UrlParserService(),
|
|
171
|
+
credentialStore,
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
const url = service.resolveConnection(
|
|
175
|
+
{
|
|
176
|
+
path: join(tempDir, '.revisium', 'revisium-cli.config.json'),
|
|
177
|
+
config: {
|
|
178
|
+
version: 1,
|
|
179
|
+
currentContext: 'cloud',
|
|
180
|
+
instances: {
|
|
181
|
+
cloud: {
|
|
182
|
+
baseUrl: 'https://cloud.revisium.io',
|
|
183
|
+
authMode: 'stored',
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
contexts: {
|
|
187
|
+
cloud: {
|
|
188
|
+
instance: 'cloud',
|
|
189
|
+
credential: 'admin',
|
|
190
|
+
organization: 'admin',
|
|
191
|
+
project: 'dictionary',
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
undefined,
|
|
197
|
+
{},
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
expect(url.auth).toEqual({ method: 'apikey', apikey: 'rev_saved' });
|
|
201
|
+
expect(credentialStore.getCredential).toHaveBeenCalledWith({
|
|
202
|
+
baseUrl: 'https://cloud.revisium.io',
|
|
203
|
+
credential: 'admin',
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('lets environment credentials override saved credentials', () => {
|
|
208
|
+
const credentialStore = {
|
|
209
|
+
getCredential: jest.fn(),
|
|
210
|
+
} as unknown as CredentialStoreService;
|
|
211
|
+
service = new WorkspaceConfigService(
|
|
212
|
+
new UrlParserService(),
|
|
213
|
+
credentialStore,
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
const url = service.resolveConnection(
|
|
217
|
+
{
|
|
218
|
+
path: join(tempDir, '.revisium', 'revisium-cli.config.json'),
|
|
219
|
+
config: {
|
|
220
|
+
version: 1,
|
|
221
|
+
currentContext: 'cloud',
|
|
222
|
+
instances: {
|
|
223
|
+
cloud: {
|
|
224
|
+
baseUrl: 'https://cloud.revisium.io',
|
|
225
|
+
authMode: 'stored',
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
contexts: {
|
|
229
|
+
cloud: {
|
|
230
|
+
instance: 'cloud',
|
|
231
|
+
credential: 'admin',
|
|
232
|
+
organization: 'admin',
|
|
233
|
+
project: 'dictionary',
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
undefined,
|
|
239
|
+
{ apikey: 'rev_env' },
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
expect(url.auth).toEqual({ method: 'apikey', apikey: 'rev_env' });
|
|
243
|
+
expect(credentialStore.getCredential).not.toHaveBeenCalled();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('fails with remediation when stored credentials cannot be read', () => {
|
|
247
|
+
const credentialStore = {
|
|
248
|
+
getCredential: jest.fn(() => {
|
|
249
|
+
throw new Error(
|
|
250
|
+
'Could not read Revisium credential "admin" for https://cloud.revisium.io: keyring unavailable',
|
|
251
|
+
);
|
|
252
|
+
}),
|
|
253
|
+
} as unknown as CredentialStoreService;
|
|
254
|
+
service = new WorkspaceConfigService(
|
|
255
|
+
new UrlParserService(),
|
|
256
|
+
credentialStore,
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
expect(() =>
|
|
260
|
+
service.resolveConnection(
|
|
261
|
+
{
|
|
262
|
+
path: join(tempDir, '.revisium', 'revisium-cli.config.json'),
|
|
263
|
+
config: {
|
|
264
|
+
version: 1,
|
|
265
|
+
currentContext: 'cloud',
|
|
266
|
+
instances: {
|
|
267
|
+
cloud: {
|
|
268
|
+
baseUrl: 'https://cloud.revisium.io',
|
|
269
|
+
authMode: 'stored',
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
contexts: {
|
|
273
|
+
cloud: {
|
|
274
|
+
instance: 'cloud',
|
|
275
|
+
credential: 'admin',
|
|
276
|
+
organization: 'admin',
|
|
277
|
+
project: 'dictionary',
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
undefined,
|
|
283
|
+
{},
|
|
284
|
+
),
|
|
285
|
+
).toThrow(
|
|
286
|
+
'No credentials found for context "cloud" credential "admin". Could not read the OS credential store',
|
|
287
|
+
);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('fails with remediation when stored credentials are required', () => {
|
|
291
|
+
expect(() =>
|
|
292
|
+
service.resolveConnection(
|
|
293
|
+
{
|
|
294
|
+
path: join(tempDir, '.revisium', 'revisium-cli.config.json'),
|
|
295
|
+
config: {
|
|
296
|
+
version: 1,
|
|
297
|
+
currentContext: 'cloud',
|
|
298
|
+
instances: {
|
|
299
|
+
cloud: {
|
|
300
|
+
baseUrl: 'https://cloud.revisium.io',
|
|
301
|
+
authMode: 'stored',
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
contexts: {
|
|
305
|
+
cloud: {
|
|
306
|
+
instance: 'cloud',
|
|
307
|
+
credential: 'admin',
|
|
308
|
+
organization: 'admin',
|
|
309
|
+
project: 'dictionary',
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
undefined,
|
|
315
|
+
{},
|
|
316
|
+
),
|
|
317
|
+
).toThrow(
|
|
318
|
+
'No credentials found for context "cloud" credential "admin". Run: revisium auth login --instance cloud --credential admin --api-key.',
|
|
319
|
+
);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('rejects malformed config files', async () => {
|
|
323
|
+
const configPath = service.getDefaultConfigPath(tempDir);
|
|
324
|
+
await mkdir(join(tempDir, '.revisium'), { recursive: true });
|
|
325
|
+
await writeFile(configPath, '{"version":2}', 'utf-8');
|
|
326
|
+
|
|
327
|
+
await expect(service.load(tempDir)).rejects.toThrow(
|
|
328
|
+
'unsupported version 2',
|
|
329
|
+
);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('normalizes instance base URLs when loading config files', async () => {
|
|
333
|
+
const configPath = service.getDefaultConfigPath(tempDir);
|
|
334
|
+
await mkdir(join(tempDir, '.revisium'), { recursive: true });
|
|
335
|
+
await writeFile(
|
|
336
|
+
configPath,
|
|
337
|
+
JSON.stringify({
|
|
338
|
+
version: 1,
|
|
339
|
+
instances: {
|
|
340
|
+
cloud: {
|
|
341
|
+
baseUrl: 'https://cloud.revisium.io/',
|
|
342
|
+
authMode: 'stored',
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
contexts: {},
|
|
346
|
+
}),
|
|
347
|
+
'utf-8',
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
const loaded = await service.load(tempDir);
|
|
351
|
+
|
|
352
|
+
expect(loaded?.config.instances.cloud.baseUrl).toBe(
|
|
353
|
+
'https://cloud.revisium.io',
|
|
354
|
+
);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('writes only the workspace config file when saving', async () => {
|
|
358
|
+
const loaded = await service.loadOrCreate(tempDir);
|
|
359
|
+
loaded.config.instances.local = {
|
|
360
|
+
baseUrl: 'http://localhost:9222',
|
|
361
|
+
authMode: 'none',
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
await service.save(loaded.path, loaded.config);
|
|
365
|
+
|
|
366
|
+
const raw = await readFile(loaded.path, 'utf-8');
|
|
367
|
+
expect(JSON.parse(raw)).toEqual({
|
|
368
|
+
version: 1,
|
|
369
|
+
instances: {
|
|
370
|
+
local: {
|
|
371
|
+
baseUrl: 'http://localhost:9222',
|
|
372
|
+
authMode: 'none',
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
contexts: {},
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export {
|
|
2
|
+
DEFAULT_BRANCH,
|
|
3
|
+
DEFAULT_CREDENTIAL,
|
|
4
|
+
DEFAULT_REVISION,
|
|
5
|
+
LoadedWorkspaceContext,
|
|
6
|
+
LoadedWorkspaceConfig,
|
|
7
|
+
WORKSPACE_CONFIG_DIR,
|
|
8
|
+
WORKSPACE_CONFIG_FILE,
|
|
9
|
+
WorkspaceAuthMode,
|
|
10
|
+
WorkspaceConfig,
|
|
11
|
+
WorkspaceConfigService,
|
|
12
|
+
WorkspaceContextConfig,
|
|
13
|
+
WorkspaceInstanceConfig,
|
|
14
|
+
} from './workspace-config.service';
|