@credibledata/cred-cli 0.0.100 → 0.0.102
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README-DEV.md +2 -2
- package/bun.lock +113 -337
- package/dist/client/api.js +2999 -1953
- package/dist/client/api.js.map +1 -1
- package/dist/client/base.js +1 -1
- package/dist/client/common.js +1 -1
- package/dist/client/configuration.js +1 -1
- package/dist/client/index.js +1 -1
- package/dist/clientconfiguration.js +59 -24
- package/dist/clientconfiguration.js.map +1 -1
- package/dist/clientconfiguration.spec.js +69 -37
- package/dist/clientconfiguration.spec.js.map +1 -1
- package/dist/commands/add.js +18 -5
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/connection/createconnections.js +4 -4
- package/dist/commands/connection/createconnections.js.map +1 -1
- package/dist/commands/connection/createconnections.spec.js +11 -1
- package/dist/commands/connection/createconnections.spec.js.map +1 -1
- package/dist/commands/connection/deleteconnection.js +4 -4
- package/dist/commands/connection/deleteconnection.js.map +1 -1
- package/dist/commands/connection/deleteconnection.spec.js +1 -1
- package/dist/commands/connection/deleteconnection.spec.js.map +1 -1
- package/dist/commands/connection/listconnections.js +4 -4
- package/dist/commands/connection/listconnections.js.map +1 -1
- package/dist/commands/connection/listconnections.spec.js +1 -1
- package/dist/commands/connection/listconnections.spec.js.map +1 -1
- package/dist/commands/{project/createproject.js → environment/createenvironment.js} +17 -14
- package/dist/commands/environment/createenvironment.js.map +1 -0
- package/dist/commands/{project/createproject.spec.js → environment/createenvironment.spec.js} +46 -46
- package/dist/commands/environment/createenvironment.spec.js.map +1 -0
- package/dist/commands/{project/deleteproject.js → environment/deleteenvironment.js} +9 -9
- package/dist/commands/environment/deleteenvironment.js.map +1 -0
- package/dist/commands/{project/deleteproject.spec.js → environment/deleteenvironment.spec.js} +29 -29
- package/dist/commands/environment/deleteenvironment.spec.js.map +1 -0
- package/dist/commands/{project/getproject.js → environment/getenvironment.js} +8 -8
- package/dist/commands/environment/getenvironment.js.map +1 -0
- package/dist/commands/{project/getproject.spec.js → environment/getenvironment.spec.js} +32 -33
- package/dist/commands/environment/getenvironment.spec.js.map +1 -0
- package/dist/commands/{project/listprojects.js → environment/listenvironments.js} +10 -10
- package/dist/commands/environment/listenvironments.js.map +1 -0
- package/dist/commands/{project/listprojects.spec.js → environment/listenvironments.spec.js} +35 -35
- package/dist/commands/environment/listenvironments.spec.js.map +1 -0
- package/dist/commands/{project/setproject.js → environment/setenvironment.js} +6 -5
- package/dist/commands/environment/setenvironment.js.map +1 -0
- package/dist/commands/{project/setproject.spec.js → environment/setenvironment.spec.js} +9 -9
- package/dist/commands/environment/setenvironment.spec.js.map +1 -0
- package/dist/commands/{project/updateproject.js → environment/updateenvironment.js} +13 -13
- package/dist/commands/environment/updateenvironment.js.map +1 -0
- package/dist/commands/{project/updateproject.spec.js → environment/updateenvironment.spec.js} +44 -44
- package/dist/commands/environment/updateenvironment.spec.js.map +1 -0
- package/dist/commands/get.js +15 -5
- package/dist/commands/get.js.map +1 -1
- package/dist/commands/legacy.js +9 -0
- package/dist/commands/legacy.js.map +1 -0
- package/dist/commands/list.js +17 -4
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/misc/login.js +2 -2
- package/dist/commands/misc/login.js.map +1 -1
- package/dist/commands/misc/login.spec.js +41 -5
- package/dist/commands/misc/login.spec.js.map +1 -1
- package/dist/commands/package/deletepackage.js +4 -4
- package/dist/commands/package/deletepackage.js.map +1 -1
- package/dist/commands/package/deletepackage.spec.js +1 -1
- package/dist/commands/package/deletepackage.spec.js.map +1 -1
- package/dist/commands/package/listpackages.js +4 -4
- package/dist/commands/package/listpackages.js.map +1 -1
- package/dist/commands/package/listpackages.spec.js +1 -1
- package/dist/commands/package/listpackages.spec.js.map +1 -1
- package/dist/commands/package/publish.js +10 -10
- package/dist/commands/package/publish.js.map +1 -1
- package/dist/commands/package/publish.spec.js +34 -6
- package/dist/commands/package/publish.spec.js.map +1 -1
- package/dist/commands/package/updatepackage.js +4 -4
- package/dist/commands/package/updatepackage.js.map +1 -1
- package/dist/commands/package/updatepackage.spec.js +1 -1
- package/dist/commands/package/updatepackage.spec.js.map +1 -1
- package/dist/commands/rm.js +15 -4
- package/dist/commands/rm.js.map +1 -1
- package/dist/commands/set.js +12 -4
- package/dist/commands/set.js.map +1 -1
- package/dist/commands/update.js +19 -6
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/version/archive-unarchive-version.js +4 -5
- package/dist/commands/version/archive-unarchive-version.js.map +1 -1
- package/dist/commands/version/archive-unarchive-version.spec.js +4 -4
- package/dist/commands/version/archive-unarchive-version.spec.js.map +1 -1
- package/dist/commands/version/listversions.js +4 -4
- package/dist/commands/version/listversions.js.map +1 -1
- package/dist/commands/version/listversions.spec.js +1 -1
- package/dist/commands/version/listversions.spec.js.map +1 -1
- package/dist/constants.js +47 -19
- package/dist/constants.js.map +1 -1
- package/dist/constants.spec.js +90 -0
- package/dist/constants.spec.js.map +1 -0
- package/dist/index.js +74 -16
- package/dist/index.js.map +1 -1
- package/package.json +7 -8
- package/src/client/api.ts +3552 -2182
- package/src/client/base.ts +1 -1
- package/src/client/common.ts +1 -1
- package/src/client/configuration.ts +1 -1
- package/src/client/index.ts +1 -1
- package/src/clientconfiguration.spec.ts +113 -114
- package/src/clientconfiguration.ts +69 -28
- package/src/commands/add.ts +35 -12
- package/src/commands/connection/createconnections.spec.ts +11 -1
- package/src/commands/connection/createconnections.ts +4 -4
- package/src/commands/connection/deleteconnection.spec.ts +1 -1
- package/src/commands/connection/deleteconnection.ts +4 -4
- package/src/commands/connection/listconnections.spec.ts +1 -1
- package/src/commands/connection/listconnections.ts +4 -4
- package/src/commands/{project/createproject.spec.ts → environment/createenvironment.spec.ts} +56 -54
- package/src/commands/{project/createproject.ts → environment/createenvironment.ts} +17 -14
- package/src/commands/{project/deleteproject.spec.ts → environment/deleteenvironment.spec.ts} +59 -30
- package/src/commands/{project/deleteproject.ts → environment/deleteenvironment.ts} +10 -10
- package/src/commands/environment/getenvironment.spec.ts +110 -0
- package/src/commands/{project/getproject.ts → environment/getenvironment.ts} +9 -9
- package/src/commands/{project/listprojects.spec.ts → environment/listenvironments.spec.ts} +37 -35
- package/src/commands/{project/listprojects.ts → environment/listenvironments.ts} +13 -8
- package/src/commands/{project/setproject.spec.ts → environment/setenvironment.spec.ts} +8 -8
- package/src/commands/{project/setproject.ts → environment/setenvironment.ts} +4 -4
- package/src/commands/{project/updateproject.spec.ts → environment/updateenvironment.spec.ts} +53 -51
- package/src/commands/{project/updateproject.ts → environment/updateenvironment.ts} +19 -17
- package/src/commands/get.ts +22 -9
- package/src/commands/legacy.ts +5 -0
- package/src/commands/list.ts +33 -9
- package/src/commands/misc/login.spec.ts +47 -5
- package/src/commands/misc/login.ts +6 -2
- package/src/commands/package/deletepackage.spec.ts +1 -1
- package/src/commands/package/deletepackage.ts +4 -4
- package/src/commands/package/listpackages.spec.ts +1 -1
- package/src/commands/package/listpackages.ts +4 -4
- package/src/commands/package/publish.spec.ts +36 -6
- package/src/commands/package/publish.ts +12 -8
- package/src/commands/package/updatepackage.spec.ts +1 -1
- package/src/commands/package/updatepackage.ts +4 -4
- package/src/commands/rm.ts +25 -9
- package/src/commands/set.ts +13 -4
- package/src/commands/update.ts +32 -12
- package/src/commands/version/archive-unarchive-version.spec.ts +4 -4
- package/src/commands/version/archive-unarchive-version.ts +4 -7
- package/src/commands/version/listversions.spec.ts +1 -1
- package/src/commands/version/listversions.ts +4 -4
- package/src/constants.spec.ts +111 -0
- package/src/constants.ts +81 -18
- package/src/index.ts +103 -17
- package/dist/commands/project/createproject.js.map +0 -1
- package/dist/commands/project/createproject.spec.js.map +0 -1
- package/dist/commands/project/deleteproject.js.map +0 -1
- package/dist/commands/project/deleteproject.spec.js.map +0 -1
- package/dist/commands/project/getproject.js.map +0 -1
- package/dist/commands/project/getproject.spec.js.map +0 -1
- package/dist/commands/project/listprojects.js.map +0 -1
- package/dist/commands/project/listprojects.spec.js.map +0 -1
- package/dist/commands/project/setproject.js.map +0 -1
- package/dist/commands/project/setproject.spec.js.map +0 -1
- package/dist/commands/project/updateproject.js.map +0 -1
- package/dist/commands/project/updateproject.spec.js.map +0 -1
- package/src/commands/project/getproject.spec.ts +0 -109
package/src/client/base.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/* eslint-disable */
|
|
3
3
|
/**
|
|
4
4
|
* Credible Admin API
|
|
5
|
-
* The Credible Admin API is a comprehensive REST API that empowers organizations to manage their Malloy data modeling ecosystem with enterprise-grade security and governance. This API provides programmatic access to all administrative functions, enabling seamless integration with existing workflows and automation systems. ## Key Features - **Organization Management**: Create and manage organizations with fine-grained access controls - **
|
|
5
|
+
* The Credible Admin API is a comprehensive REST API that empowers organizations to manage their Malloy data modeling ecosystem with enterprise-grade security and governance. This API provides programmatic access to all administrative functions, enabling seamless integration with existing workflows and automation systems. ## Key Features - **Organization Management**: Create and manage organizations with fine-grained access controls - **Environment & Package Lifecycle**: Full CRUD operations for environments, packages, and versions - **Connection Management**: Secure database connection configuration and management - **Permission Management**: Granular role-based access control (RBAC) at organization, environment, package, workspace, and document levels - **Workspace Management**: Collaborative workspaces for data modeling and analysis - **User & Group Management**: Comprehensive user administration with group-based permissions ## Resource Hierarchy The API follows a hierarchical resource structure with fine-grained permission management at each level: ``` Organizations ├── Permissions ├── Environments │ ├── Permissions │ ├── Packages │ │ ├── Permissions │ │ └── Versions │ └── Connections ├── Workspaces │ ├── Permissions │ └── Documents │ └── Permissions └── Groups ├── Permissions └── Members System-Level Resources: ├── Users ├── System Permissions └── Demo Operations ``` ## Authentication & Authorization All API endpoints require proper authentication. The API implements fine-grained authorization using role-based permissions: - **Admin**: Full access to all resources within scope - **Modeler**: Can create and modify data models and packages - **Viewer**: Read-only access to resources - **Manager**: Workspace management capabilities - **Editor**: Document editing permissions ## Rate Limiting & Best Practices - API requests are rate-limited to ensure system stability - Implement proper error handling and retry logic - Cache responses when appropriate to reduce API calls ## Support & Documentation For additional support, examples, and integration guides, visit our developer documentation or contact our support team.
|
|
6
6
|
*
|
|
7
7
|
* The version of the OpenAPI document: v0
|
|
8
8
|
* Contact: support@credibledata.com
|
package/src/client/common.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/* eslint-disable */
|
|
3
3
|
/**
|
|
4
4
|
* Credible Admin API
|
|
5
|
-
* The Credible Admin API is a comprehensive REST API that empowers organizations to manage their Malloy data modeling ecosystem with enterprise-grade security and governance. This API provides programmatic access to all administrative functions, enabling seamless integration with existing workflows and automation systems. ## Key Features - **Organization Management**: Create and manage organizations with fine-grained access controls - **
|
|
5
|
+
* The Credible Admin API is a comprehensive REST API that empowers organizations to manage their Malloy data modeling ecosystem with enterprise-grade security and governance. This API provides programmatic access to all administrative functions, enabling seamless integration with existing workflows and automation systems. ## Key Features - **Organization Management**: Create and manage organizations with fine-grained access controls - **Environment & Package Lifecycle**: Full CRUD operations for environments, packages, and versions - **Connection Management**: Secure database connection configuration and management - **Permission Management**: Granular role-based access control (RBAC) at organization, environment, package, workspace, and document levels - **Workspace Management**: Collaborative workspaces for data modeling and analysis - **User & Group Management**: Comprehensive user administration with group-based permissions ## Resource Hierarchy The API follows a hierarchical resource structure with fine-grained permission management at each level: ``` Organizations ├── Permissions ├── Environments │ ├── Permissions │ ├── Packages │ │ ├── Permissions │ │ └── Versions │ └── Connections ├── Workspaces │ ├── Permissions │ └── Documents │ └── Permissions └── Groups ├── Permissions └── Members System-Level Resources: ├── Users ├── System Permissions └── Demo Operations ``` ## Authentication & Authorization All API endpoints require proper authentication. The API implements fine-grained authorization using role-based permissions: - **Admin**: Full access to all resources within scope - **Modeler**: Can create and modify data models and packages - **Viewer**: Read-only access to resources - **Manager**: Workspace management capabilities - **Editor**: Document editing permissions ## Rate Limiting & Best Practices - API requests are rate-limited to ensure system stability - Implement proper error handling and retry logic - Cache responses when appropriate to reduce API calls ## Support & Documentation For additional support, examples, and integration guides, visit our developer documentation or contact our support team.
|
|
6
6
|
*
|
|
7
7
|
* The version of the OpenAPI document: v0
|
|
8
8
|
* Contact: support@credibledata.com
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/* eslint-disable */
|
|
3
3
|
/**
|
|
4
4
|
* Credible Admin API
|
|
5
|
-
* The Credible Admin API is a comprehensive REST API that empowers organizations to manage their Malloy data modeling ecosystem with enterprise-grade security and governance. This API provides programmatic access to all administrative functions, enabling seamless integration with existing workflows and automation systems. ## Key Features - **Organization Management**: Create and manage organizations with fine-grained access controls - **
|
|
5
|
+
* The Credible Admin API is a comprehensive REST API that empowers organizations to manage their Malloy data modeling ecosystem with enterprise-grade security and governance. This API provides programmatic access to all administrative functions, enabling seamless integration with existing workflows and automation systems. ## Key Features - **Organization Management**: Create and manage organizations with fine-grained access controls - **Environment & Package Lifecycle**: Full CRUD operations for environments, packages, and versions - **Connection Management**: Secure database connection configuration and management - **Permission Management**: Granular role-based access control (RBAC) at organization, environment, package, workspace, and document levels - **Workspace Management**: Collaborative workspaces for data modeling and analysis - **User & Group Management**: Comprehensive user administration with group-based permissions ## Resource Hierarchy The API follows a hierarchical resource structure with fine-grained permission management at each level: ``` Organizations ├── Permissions ├── Environments │ ├── Permissions │ ├── Packages │ │ ├── Permissions │ │ └── Versions │ └── Connections ├── Workspaces │ ├── Permissions │ └── Documents │ └── Permissions └── Groups ├── Permissions └── Members System-Level Resources: ├── Users ├── System Permissions └── Demo Operations ``` ## Authentication & Authorization All API endpoints require proper authentication. The API implements fine-grained authorization using role-based permissions: - **Admin**: Full access to all resources within scope - **Modeler**: Can create and modify data models and packages - **Viewer**: Read-only access to resources - **Manager**: Workspace management capabilities - **Editor**: Document editing permissions ## Rate Limiting & Best Practices - API requests are rate-limited to ensure system stability - Implement proper error handling and retry logic - Cache responses when appropriate to reduce API calls ## Support & Documentation For additional support, examples, and integration guides, visit our developer documentation or contact our support team.
|
|
6
6
|
*
|
|
7
7
|
* The version of the OpenAPI document: v0
|
|
8
8
|
* Contact: support@credibledata.com
|
package/src/client/index.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/* eslint-disable */
|
|
3
3
|
/**
|
|
4
4
|
* Credible Admin API
|
|
5
|
-
* The Credible Admin API is a comprehensive REST API that empowers organizations to manage their Malloy data modeling ecosystem with enterprise-grade security and governance. This API provides programmatic access to all administrative functions, enabling seamless integration with existing workflows and automation systems. ## Key Features - **Organization Management**: Create and manage organizations with fine-grained access controls - **
|
|
5
|
+
* The Credible Admin API is a comprehensive REST API that empowers organizations to manage their Malloy data modeling ecosystem with enterprise-grade security and governance. This API provides programmatic access to all administrative functions, enabling seamless integration with existing workflows and automation systems. ## Key Features - **Organization Management**: Create and manage organizations with fine-grained access controls - **Environment & Package Lifecycle**: Full CRUD operations for environments, packages, and versions - **Connection Management**: Secure database connection configuration and management - **Permission Management**: Granular role-based access control (RBAC) at organization, environment, package, workspace, and document levels - **Workspace Management**: Collaborative workspaces for data modeling and analysis - **User & Group Management**: Comprehensive user administration with group-based permissions ## Resource Hierarchy The API follows a hierarchical resource structure with fine-grained permission management at each level: ``` Organizations ├── Permissions ├── Environments │ ├── Permissions │ ├── Packages │ │ ├── Permissions │ │ └── Versions │ └── Connections ├── Workspaces │ ├── Permissions │ └── Documents │ └── Permissions └── Groups ├── Permissions └── Members System-Level Resources: ├── Users ├── System Permissions └── Demo Operations ``` ## Authentication & Authorization All API endpoints require proper authentication. The API implements fine-grained authorization using role-based permissions: - **Admin**: Full access to all resources within scope - **Modeler**: Can create and modify data models and packages - **Viewer**: Read-only access to resources - **Manager**: Workspace management capabilities - **Editor**: Document editing permissions ## Rate Limiting & Best Practices - API requests are rate-limited to ensure system stability - Implement proper error handling and retry logic - Cache responses when appropriate to reduce API calls ## Support & Documentation For additional support, examples, and integration guides, visit our developer documentation or contact our support team.
|
|
6
6
|
*
|
|
7
7
|
* The version of the OpenAPI document: v0
|
|
8
8
|
* Contact: support@credibledata.com
|
|
@@ -22,8 +22,21 @@ describe("ClientConfiguration", () => {
|
|
|
22
22
|
let clientConfig: ClientConfiguration;
|
|
23
23
|
let readFileSyncStub: sinon.SinonStub;
|
|
24
24
|
let writeFileSyncStub: sinon.SinonStub;
|
|
25
|
+
let renameSyncStub: sinon.SinonStub;
|
|
25
26
|
let customConfigPath: string;
|
|
26
27
|
|
|
28
|
+
// Simulate a real ENOENT from readFileSync (has `.code = "ENOENT"`).
|
|
29
|
+
// A bare `Error("File not found")` no longer triggers first-time-setup —
|
|
30
|
+
// loadConfig now rethrows non-ENOENT errors instead of silently masking them.
|
|
31
|
+
const enoent = () => Object.assign(new Error("ENOENT"), { code: "ENOENT" });
|
|
32
|
+
|
|
33
|
+
// Pull the most-recent persisted config payload off the stubs. Atomic writes
|
|
34
|
+
// go through writeFileSync(tmpPath, json) followed by renameSync(tmp, path).
|
|
35
|
+
const lastWrittenConfig = () => {
|
|
36
|
+
const args = writeFileSyncStub.lastCall.args;
|
|
37
|
+
return JSON.parse(args[1] as string);
|
|
38
|
+
};
|
|
39
|
+
|
|
27
40
|
beforeEach(() => {
|
|
28
41
|
customConfigPath = join(os.homedir(), ".cred_test");
|
|
29
42
|
clientConfig = new ClientConfiguration(customConfigPath);
|
|
@@ -32,6 +45,7 @@ describe("ClientConfiguration", () => {
|
|
|
32
45
|
|
|
33
46
|
readFileSyncStub = sinon.stub(fs, "readFileSync");
|
|
34
47
|
writeFileSyncStub = sinon.stub(fs, "writeFileSync");
|
|
48
|
+
renameSyncStub = sinon.stub(fs, "renameSync");
|
|
35
49
|
});
|
|
36
50
|
|
|
37
51
|
afterEach(() => {
|
|
@@ -43,7 +57,7 @@ describe("ClientConfiguration", () => {
|
|
|
43
57
|
});
|
|
44
58
|
|
|
45
59
|
it("should load the default config if the file does not exist", () => {
|
|
46
|
-
readFileSyncStub.throws(
|
|
60
|
+
readFileSyncStub.throws(enoent());
|
|
47
61
|
clientConfig.loadConfig();
|
|
48
62
|
expect(clientConfig["config"]).to.deep.equal({
|
|
49
63
|
configPerEnvironment: fullPerEnv(),
|
|
@@ -51,6 +65,21 @@ describe("ClientConfiguration", () => {
|
|
|
51
65
|
});
|
|
52
66
|
});
|
|
53
67
|
|
|
68
|
+
it("should throw loudly when the config file is unparseable", () => {
|
|
69
|
+
// Non-atomic writes from older binaries can leave `.cred` truncated.
|
|
70
|
+
// loadConfig must fail loud instead of silently overwriting — a silent
|
|
71
|
+
// reset erases the access token for every subsequent caller.
|
|
72
|
+
readFileSyncStub.returns("{ not json");
|
|
73
|
+
expect(() => clientConfig.loadConfig()).to.throw(/may be corrupt/);
|
|
74
|
+
expect(writeFileSyncStub.called).to.be.false;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("should throw when readFileSync fails with a non-ENOENT error", () => {
|
|
78
|
+
const eacces = Object.assign(new Error("EACCES"), { code: "EACCES" });
|
|
79
|
+
readFileSyncStub.throws(eacces);
|
|
80
|
+
expect(() => clientConfig.loadConfig()).to.throw(/EACCES/);
|
|
81
|
+
});
|
|
82
|
+
|
|
54
83
|
it("should load the config file correctly", () => {
|
|
55
84
|
const mockConfig = {
|
|
56
85
|
configPerEnvironment: {
|
|
@@ -92,134 +121,106 @@ describe("ClientConfiguration", () => {
|
|
|
92
121
|
});
|
|
93
122
|
|
|
94
123
|
it("should set and get the project correctly", () => {
|
|
95
|
-
readFileSyncStub.throws(
|
|
124
|
+
readFileSyncStub.throws(enoent());
|
|
96
125
|
clientConfig.setProject("new-project");
|
|
97
126
|
|
|
98
|
-
expect(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
activeEnvironment: "production",
|
|
106
|
-
}),
|
|
107
|
-
),
|
|
108
|
-
).to.be.true;
|
|
127
|
+
expect(lastWrittenConfig()).to.deep.equal({
|
|
128
|
+
configPerEnvironment: fullPerEnv({
|
|
129
|
+
production: { project: "new-project" },
|
|
130
|
+
}),
|
|
131
|
+
activeEnvironment: "production",
|
|
132
|
+
});
|
|
133
|
+
expect(renameSyncStub.lastCall.args[1]).to.equal(customConfigPath);
|
|
109
134
|
});
|
|
110
135
|
|
|
111
136
|
it("should set and get the access token correctly", () => {
|
|
112
|
-
readFileSyncStub.throws(
|
|
137
|
+
readFileSyncStub.throws(enoent());
|
|
113
138
|
clientConfig.setAccessToken(false, "token-123");
|
|
114
139
|
|
|
115
|
-
expect(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}),
|
|
125
|
-
activeEnvironment: "production",
|
|
126
|
-
}),
|
|
127
|
-
),
|
|
128
|
-
).to.be.true;
|
|
140
|
+
expect(lastWrittenConfig()).to.deep.equal({
|
|
141
|
+
configPerEnvironment: fullPerEnv({
|
|
142
|
+
production: {
|
|
143
|
+
isServiceAccountToken: true,
|
|
144
|
+
accessToken: "token-123",
|
|
145
|
+
},
|
|
146
|
+
}),
|
|
147
|
+
activeEnvironment: "production",
|
|
148
|
+
});
|
|
129
149
|
});
|
|
130
150
|
|
|
131
|
-
it("should
|
|
151
|
+
it("should preserve other per-env fields when setting access token", () => {
|
|
152
|
+
// Valid initial config (with an activeEnvironment so loadConfig accepts it).
|
|
132
153
|
readFileSyncStub.returns(
|
|
133
|
-
JSON.stringify({
|
|
154
|
+
JSON.stringify({
|
|
155
|
+
configPerEnvironment: {
|
|
156
|
+
production: {
|
|
157
|
+
auth: { username: "user", password: "pass" },
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
activeEnvironment: "production",
|
|
161
|
+
}),
|
|
134
162
|
);
|
|
135
163
|
clientConfig.setAccessToken(true, "new-token");
|
|
136
|
-
expect(
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
accessToken: "new-token",
|
|
144
|
-
},
|
|
145
|
-
}),
|
|
146
|
-
activeEnvironment: "production",
|
|
147
|
-
}),
|
|
148
|
-
),
|
|
149
|
-
).to.be.true;
|
|
164
|
+
expect(lastWrittenConfig().configPerEnvironment.production).to.deep.equal(
|
|
165
|
+
{
|
|
166
|
+
auth: { username: "user", password: "pass" },
|
|
167
|
+
isServiceAccountToken: false,
|
|
168
|
+
accessToken: "new-token",
|
|
169
|
+
},
|
|
170
|
+
);
|
|
150
171
|
});
|
|
151
172
|
|
|
152
173
|
it("should set and get organization", () => {
|
|
153
|
-
readFileSyncStub.throws(
|
|
174
|
+
readFileSyncStub.throws(enoent());
|
|
154
175
|
clientConfig.setOrganization("my-org");
|
|
155
176
|
|
|
156
|
-
expect(
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}),
|
|
163
|
-
activeEnvironment: "production",
|
|
164
|
-
}),
|
|
165
|
-
),
|
|
166
|
-
).to.be.true;
|
|
177
|
+
expect(lastWrittenConfig()).to.deep.equal({
|
|
178
|
+
configPerEnvironment: fullPerEnv({
|
|
179
|
+
production: { organization: "my-org" },
|
|
180
|
+
}),
|
|
181
|
+
activeEnvironment: "production",
|
|
182
|
+
});
|
|
167
183
|
});
|
|
168
184
|
|
|
169
185
|
it("should set and get refresh token", () => {
|
|
170
|
-
readFileSyncStub.throws(
|
|
186
|
+
readFileSyncStub.throws(enoent());
|
|
171
187
|
clientConfig.setRefreshToken("refresh-token-123");
|
|
172
188
|
|
|
173
|
-
expect(
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}),
|
|
180
|
-
activeEnvironment: "production",
|
|
181
|
-
}),
|
|
182
|
-
),
|
|
183
|
-
).to.be.true;
|
|
189
|
+
expect(lastWrittenConfig()).to.deep.equal({
|
|
190
|
+
configPerEnvironment: fullPerEnv({
|
|
191
|
+
production: { refreshToken: "refresh-token-123" },
|
|
192
|
+
}),
|
|
193
|
+
activeEnvironment: "production",
|
|
194
|
+
});
|
|
184
195
|
});
|
|
185
196
|
|
|
186
197
|
it("should set and get VSCode user", () => {
|
|
187
|
-
readFileSyncStub.throws(
|
|
198
|
+
readFileSyncStub.throws(enoent());
|
|
188
199
|
clientConfig.setVSCodeUser("testuser", "test@example.com");
|
|
189
200
|
|
|
190
|
-
expect(
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}),
|
|
202
|
-
activeEnvironment: "production",
|
|
203
|
-
}),
|
|
204
|
-
),
|
|
205
|
-
).to.be.true;
|
|
201
|
+
expect(lastWrittenConfig()).to.deep.equal({
|
|
202
|
+
configPerEnvironment: fullPerEnv({
|
|
203
|
+
production: {
|
|
204
|
+
vsCodeUser: {
|
|
205
|
+
username: "testuser",
|
|
206
|
+
email: "test@example.com",
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
}),
|
|
210
|
+
activeEnvironment: "production",
|
|
211
|
+
});
|
|
206
212
|
});
|
|
207
213
|
|
|
208
214
|
it("should set and get JWT access token", () => {
|
|
209
|
-
readFileSyncStub.throws(
|
|
215
|
+
readFileSyncStub.throws(enoent());
|
|
210
216
|
clientConfig.setJwtAccessToken("jwt-token-abc");
|
|
211
217
|
|
|
212
|
-
expect(
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}),
|
|
219
|
-
activeEnvironment: "production",
|
|
220
|
-
}),
|
|
221
|
-
),
|
|
222
|
-
).to.be.true;
|
|
218
|
+
expect(lastWrittenConfig()).to.deep.equal({
|
|
219
|
+
configPerEnvironment: fullPerEnv({
|
|
220
|
+
production: { jwtAccessToken: "jwt-token-abc" },
|
|
221
|
+
}),
|
|
222
|
+
activeEnvironment: "production",
|
|
223
|
+
});
|
|
223
224
|
});
|
|
224
225
|
|
|
225
226
|
it("should return true for doesConfigExists when environment config exists", () => {
|
|
@@ -248,26 +249,20 @@ describe("ClientConfiguration", () => {
|
|
|
248
249
|
readFileSyncStub.returns(JSON.stringify(mockConfig));
|
|
249
250
|
clientConfig.destroyConfig();
|
|
250
251
|
|
|
251
|
-
|
|
252
|
-
expect(JSON.parse(lastWrite)).to.deep.equal({
|
|
252
|
+
expect(lastWrittenConfig()).to.deep.equal({
|
|
253
253
|
configPerEnvironment: fullPerEnv(),
|
|
254
254
|
activeEnvironment: "production",
|
|
255
255
|
});
|
|
256
256
|
});
|
|
257
257
|
|
|
258
258
|
it("should set and get environment", () => {
|
|
259
|
-
readFileSyncStub.throws(
|
|
259
|
+
readFileSyncStub.throws(enoent());
|
|
260
260
|
clientConfig.setEnvironment("staging");
|
|
261
261
|
|
|
262
|
-
expect(
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
configPerEnvironment: fullPerEnv(),
|
|
267
|
-
activeEnvironment: "staging",
|
|
268
|
-
}),
|
|
269
|
-
),
|
|
270
|
-
).to.be.true;
|
|
262
|
+
expect(lastWrittenConfig()).to.deep.equal({
|
|
263
|
+
configPerEnvironment: fullPerEnv(),
|
|
264
|
+
activeEnvironment: "staging",
|
|
265
|
+
});
|
|
271
266
|
});
|
|
272
267
|
|
|
273
268
|
it("should return headers with Bearer token for user tokens", () => {
|
|
@@ -308,11 +303,14 @@ describe("ClientConfiguration", () => {
|
|
|
308
303
|
expect(headers.Authorization).to.equal("ApiKey service-token");
|
|
309
304
|
});
|
|
310
305
|
|
|
311
|
-
it("should
|
|
306
|
+
it("should throw when config file is missing activeEnvironment", () => {
|
|
307
|
+
// Previously loadConfig silently reset to defaults — that turned one
|
|
308
|
+
// corrupt read into permanent state loss for every caller. Now we fail loud.
|
|
312
309
|
readFileSyncStub.returns(JSON.stringify({ configPerEnvironment: {} }));
|
|
313
|
-
clientConfig.loadConfig()
|
|
314
|
-
|
|
315
|
-
|
|
310
|
+
expect(() => clientConfig.loadConfig()).to.throw(
|
|
311
|
+
/Invalid config structure/,
|
|
312
|
+
);
|
|
313
|
+
expect(writeFileSyncStub.called).to.be.false;
|
|
316
314
|
});
|
|
317
315
|
|
|
318
316
|
describe("isTokenValid", () => {
|
|
@@ -466,7 +464,8 @@ describe("ClientConfiguration", () => {
|
|
|
466
464
|
|
|
467
465
|
expect(loggerStub.info.calledWith(sinon.match("Organization:"))).to.be
|
|
468
466
|
.true;
|
|
469
|
-
expect(loggerStub.info.calledWith(sinon.match("
|
|
467
|
+
expect(loggerStub.info.calledWith(sinon.match("Environment:"))).to.be
|
|
468
|
+
.true;
|
|
470
469
|
});
|
|
471
470
|
|
|
472
471
|
it("should print error when token is invalid", () => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
1
2
|
import fs from "fs";
|
|
2
3
|
import { jwtDecode } from "jwt-decode";
|
|
3
4
|
import kleur from "kleur";
|
|
@@ -44,28 +45,35 @@ export class ClientConfiguration {
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
public loadConfig(): ConfigFile {
|
|
48
|
+
let fileContents: Buffer | undefined;
|
|
47
49
|
try {
|
|
48
|
-
|
|
49
|
-
this.config = JSON.parse(fileContents.toString());
|
|
50
|
-
if (
|
|
51
|
-
!this.config.activeEnvironment ||
|
|
52
|
-
!this.config.configPerEnvironment
|
|
53
|
-
) {
|
|
54
|
-
throw new Error(
|
|
55
|
-
"Corrupted config file found, restoring to default settings.",
|
|
56
|
-
);
|
|
57
|
-
}
|
|
50
|
+
fileContents = fs.readFileSync(this.configPath);
|
|
58
51
|
} catch (error) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
52
|
+
if ((error as NodeJS.ErrnoException)?.code === "ENOENT") {
|
|
53
|
+
// First-time setup: create a default config on disk.
|
|
54
|
+
this.config = this.defaultConfig();
|
|
55
|
+
this.writeConfig();
|
|
56
|
+
return this.config;
|
|
63
57
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
this.config = JSON.parse(fileContents.toString());
|
|
63
|
+
} catch (error) {
|
|
64
|
+
// Non-atomic writes from older binaries, or a manually corrupted file,
|
|
65
|
+
// can leave `.cred` unparseable. Fail loud rather than silently
|
|
66
|
+
// overwriting — a silent reset erases the access token for every
|
|
67
|
+
// subsequent caller on the same machine.
|
|
68
|
+
throw new Error(
|
|
69
|
+
`Failed to parse ${this.configPath}: ${error}. The file may be corrupt; re-run \`cred login\` to reset.`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!this.config.activeEnvironment || !this.config.configPerEnvironment) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`Invalid config structure in ${this.configPath}; re-run \`cred login\` to reset.`,
|
|
76
|
+
);
|
|
69
77
|
}
|
|
70
78
|
|
|
71
79
|
for (const env of ALL_ENVIRONMENTS) {
|
|
@@ -77,13 +85,46 @@ export class ClientConfiguration {
|
|
|
77
85
|
return this.config;
|
|
78
86
|
}
|
|
79
87
|
|
|
88
|
+
private defaultConfig(): ConfigFile {
|
|
89
|
+
const configPerEnvironment: ConfigFile["configPerEnvironment"] = {};
|
|
90
|
+
for (const env of ALL_ENVIRONMENTS) {
|
|
91
|
+
configPerEnvironment[env] = {};
|
|
92
|
+
}
|
|
93
|
+
return { configPerEnvironment, activeEnvironment: "production" };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Atomic: write to a sibling temp file, then rename into place.
|
|
97
|
+
// Readers either see the old file or the new file, never a truncated
|
|
98
|
+
// zero-byte snapshot (the window that `writeFileSync` exposes between
|
|
99
|
+
// open(O_TRUNC) and write).
|
|
100
|
+
//
|
|
101
|
+
// The temp suffix uses 16 bytes of crypto-random hex so the temp
|
|
102
|
+
// path is unguessable — a predictable name (pid + Date.now()) lets a
|
|
103
|
+
// co-located attacker pre-create a symlink at that path and trick
|
|
104
|
+
// us into clobbering an arbitrary file (CodeQL js/insecure-temporary-file).
|
|
105
|
+
// O_EXCL on open further refuses to follow a pre-existing entry.
|
|
106
|
+
private writeConfig(): void {
|
|
107
|
+
const tmp = `${this.configPath}.${crypto.randomBytes(16).toString("hex")}.tmp`;
|
|
108
|
+
const fd = fs.openSync(
|
|
109
|
+
tmp,
|
|
110
|
+
fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL,
|
|
111
|
+
0o600,
|
|
112
|
+
);
|
|
113
|
+
try {
|
|
114
|
+
fs.writeFileSync(fd, JSON.stringify(this.config));
|
|
115
|
+
} finally {
|
|
116
|
+
fs.closeSync(fd);
|
|
117
|
+
}
|
|
118
|
+
fs.renameSync(tmp, this.configPath);
|
|
119
|
+
}
|
|
120
|
+
|
|
80
121
|
public setOrganization(organizationName: string) {
|
|
81
122
|
this.loadConfig();
|
|
82
123
|
// TODO: Validate organization exists and user has access.
|
|
83
124
|
const environment = this.getEnvironment();
|
|
84
125
|
this.config.configPerEnvironment[environment].organization =
|
|
85
126
|
organizationName;
|
|
86
|
-
|
|
127
|
+
this.writeConfig();
|
|
87
128
|
}
|
|
88
129
|
|
|
89
130
|
public setProject(projectName: string) {
|
|
@@ -91,7 +132,7 @@ export class ClientConfiguration {
|
|
|
91
132
|
// TODO: Validate project exists and user has access.
|
|
92
133
|
const environment = this.getEnvironment();
|
|
93
134
|
this.config.configPerEnvironment[environment].project = projectName;
|
|
94
|
-
|
|
135
|
+
this.writeConfig();
|
|
95
136
|
}
|
|
96
137
|
|
|
97
138
|
public getOrganization() {
|
|
@@ -112,7 +153,7 @@ export class ClientConfiguration {
|
|
|
112
153
|
this.config.configPerEnvironment[environment].isServiceAccountToken =
|
|
113
154
|
!userToken;
|
|
114
155
|
this.config.configPerEnvironment[environment].accessToken = accessToken;
|
|
115
|
-
|
|
156
|
+
this.writeConfig();
|
|
116
157
|
}
|
|
117
158
|
|
|
118
159
|
public getAuthenticationHeader() {
|
|
@@ -135,7 +176,7 @@ export class ClientConfiguration {
|
|
|
135
176
|
const environment = this.getEnvironment();
|
|
136
177
|
this.config.configPerEnvironment[environment].jwtAccessToken =
|
|
137
178
|
jwtAccessToken;
|
|
138
|
-
|
|
179
|
+
this.writeConfig();
|
|
139
180
|
}
|
|
140
181
|
|
|
141
182
|
public getJwtAccessToken(): string | undefined {
|
|
@@ -154,7 +195,7 @@ export class ClientConfiguration {
|
|
|
154
195
|
username,
|
|
155
196
|
email,
|
|
156
197
|
};
|
|
157
|
-
|
|
198
|
+
this.writeConfig();
|
|
158
199
|
}
|
|
159
200
|
|
|
160
201
|
public getVSCodeUser(): { username: string; email: string } | undefined {
|
|
@@ -167,7 +208,7 @@ export class ClientConfiguration {
|
|
|
167
208
|
this.loadConfig();
|
|
168
209
|
const environment = this.getEnvironment();
|
|
169
210
|
this.config.configPerEnvironment[environment].refreshToken = refreshToken;
|
|
170
|
-
|
|
211
|
+
this.writeConfig();
|
|
171
212
|
}
|
|
172
213
|
|
|
173
214
|
public getRefreshToken(): string | undefined {
|
|
@@ -192,13 +233,13 @@ export class ClientConfiguration {
|
|
|
192
233
|
this.loadConfig();
|
|
193
234
|
const environment = this.getEnvironment();
|
|
194
235
|
this.config.configPerEnvironment[environment] = {};
|
|
195
|
-
|
|
236
|
+
this.writeConfig();
|
|
196
237
|
}
|
|
197
238
|
|
|
198
239
|
public setEnvironment(environment: Environment) {
|
|
199
240
|
this.loadConfig();
|
|
200
241
|
this.config.activeEnvironment = environment;
|
|
201
|
-
|
|
242
|
+
this.writeConfig();
|
|
202
243
|
}
|
|
203
244
|
|
|
204
245
|
public getEnvironment() {
|
|
@@ -252,7 +293,7 @@ export class ClientConfiguration {
|
|
|
252
293
|
|
|
253
294
|
console.log(kleur.green(ASCII_TITLE));
|
|
254
295
|
logger.info(`Organization: ${kleur.green(organization || "Not set")}`);
|
|
255
|
-
logger.info(`
|
|
296
|
+
logger.info(`Environment: ${kleur.green(project || "Not set")}`);
|
|
256
297
|
} else {
|
|
257
298
|
logger.error("Failed to get status");
|
|
258
299
|
logger.error(
|
package/src/commands/add.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
1
2
|
import { Command } from "commander";
|
|
2
3
|
import { controlPlanesByEnv, Environment } from "../constants";
|
|
3
4
|
import { createConnections } from "./connection/createconnections";
|
|
4
5
|
import { addMember } from "./group/addmember";
|
|
5
6
|
import { createGroup } from "./group/creategroup";
|
|
6
7
|
import { createGroupAccessToken } from "./groupaccesstoken/createToken";
|
|
7
|
-
import {
|
|
8
|
+
import { createEnvironment } from "./environment/createenvironment";
|
|
9
|
+
import { warnProjectDeprecated } from "./legacy";
|
|
8
10
|
|
|
9
11
|
export const registerAddCommands = (cli: Command, environment: Environment) => {
|
|
10
12
|
const add = cli
|
|
@@ -12,14 +14,41 @@ export const registerAddCommands = (cli: Command, environment: Environment) => {
|
|
|
12
14
|
.description("Add a resource")
|
|
13
15
|
.argument("<resource>", "The resource to add");
|
|
14
16
|
|
|
17
|
+
const addEnvironmentAction = (environmentName: string, options: any) => {
|
|
18
|
+
createEnvironment(
|
|
19
|
+
controlPlanesByEnv[environment],
|
|
20
|
+
environmentName,
|
|
21
|
+
options.readmeFile,
|
|
22
|
+
options.replication,
|
|
23
|
+
options.debug,
|
|
24
|
+
options.yes,
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
add.command("environment")
|
|
29
|
+
.description("Add an environment.")
|
|
30
|
+
.argument("<environmentName>", "The name of the environment to create.")
|
|
31
|
+
.option("--debug")
|
|
32
|
+
.option("-y, --yes", "Skip confirmation.")
|
|
33
|
+
.option(
|
|
34
|
+
"--readmeFile <readmeFile>",
|
|
35
|
+
"The path to the readme file for the environment.",
|
|
36
|
+
)
|
|
37
|
+
.option(
|
|
38
|
+
"--replication <replicationCount>",
|
|
39
|
+
"The replication count (must be at least 1).",
|
|
40
|
+
parseInt,
|
|
41
|
+
)
|
|
42
|
+
.action(addEnvironmentAction);
|
|
43
|
+
|
|
15
44
|
add.command("project")
|
|
16
|
-
.description("
|
|
17
|
-
.argument("<projectName>", "The name of the
|
|
45
|
+
.description("[deprecated] Alias for 'add environment'.")
|
|
46
|
+
.argument("<projectName>", "The name of the environment to create.")
|
|
18
47
|
.option("--debug")
|
|
19
48
|
.option("-y, --yes", "Skip confirmation.")
|
|
20
49
|
.option(
|
|
21
50
|
"--readmeFile <readmeFile>",
|
|
22
|
-
"The path to the readme file for the
|
|
51
|
+
"The path to the readme file for the environment.",
|
|
23
52
|
)
|
|
24
53
|
.option(
|
|
25
54
|
"--replication <replicationCount>",
|
|
@@ -27,14 +56,8 @@ export const registerAddCommands = (cli: Command, environment: Environment) => {
|
|
|
27
56
|
parseInt,
|
|
28
57
|
)
|
|
29
58
|
.action((projectName, options) => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
projectName,
|
|
33
|
-
options.readmeFile,
|
|
34
|
-
options.replication,
|
|
35
|
-
options.debug,
|
|
36
|
-
options.yes,
|
|
37
|
-
);
|
|
59
|
+
warnProjectDeprecated("add environment");
|
|
60
|
+
addEnvironmentAction(projectName, options);
|
|
38
61
|
});
|
|
39
62
|
|
|
40
63
|
add.command("connection")
|