clever-tools 4.5.2 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +10 -0
  2. package/bin/clever.js +25 -22
  3. package/package.json +2 -2
  4. package/src/clever-client/auth-bridge.js +0 -25
  5. package/src/commands/README.md +1 -0
  6. package/src/commands/accesslogs/accesslogs.command.js +4 -1
  7. package/src/commands/accesslogs/accesslogs.docs.md +1 -1
  8. package/src/commands/addon/addon.create.command.js +23 -20
  9. package/src/commands/config/config.set.command.js +3 -3
  10. package/src/commands/config-provider/config-provider.args.js +8 -0
  11. package/src/commands/config-provider/config-provider.command.js +10 -0
  12. package/src/commands/config-provider/config-provider.docs.md +109 -0
  13. package/src/commands/config-provider/config-provider.get.command.js +36 -0
  14. package/src/commands/config-provider/config-provider.import.command.js +40 -0
  15. package/src/commands/config-provider/config-provider.list.command.js +45 -0
  16. package/src/commands/config-provider/config-provider.open.command.js +17 -0
  17. package/src/commands/config-provider/config-provider.rm.command.js +25 -0
  18. package/src/commands/config-provider/config-provider.set.command.js +37 -0
  19. package/src/commands/create/create.command.js +7 -6
  20. package/src/commands/curl/curl.command.js +18 -19
  21. package/src/commands/deploy/deploy.command.js +14 -8
  22. package/src/commands/deploy/deploy.docs.md +20 -0
  23. package/src/commands/diag/diag.command.js +52 -39
  24. package/src/commands/features/features.disable.command.js +1 -2
  25. package/src/commands/features/features.enable.command.js +1 -2
  26. package/src/commands/features/features.info.command.js +1 -1
  27. package/src/commands/features/features.list.command.js +1 -2
  28. package/src/commands/global.commands.js +22 -0
  29. package/src/commands/global.options.js +1 -1
  30. package/src/commands/login/login.command.js +119 -20
  31. package/src/commands/login/login.docs.md +9 -2
  32. package/src/commands/logout/logout.command.js +39 -7
  33. package/src/commands/logout/logout.docs.md +7 -1
  34. package/src/commands/logs/logs.command.js +7 -8
  35. package/src/commands/logs/logs.docs.md +1 -1
  36. package/src/commands/ng/ng.get-config.command.js +3 -3
  37. package/src/commands/profile/profile.command.js +13 -37
  38. package/src/commands/profile/profile.docs.md +28 -0
  39. package/src/commands/profile/profile.list.command.js +44 -0
  40. package/src/commands/profile/profile.switch.command.js +80 -0
  41. package/src/commands/restart/restart.command.js +3 -2
  42. package/src/commands/ssh/ssh.command.js +2 -2
  43. package/src/commands/tokens/tokens.command.js +3 -3
  44. package/src/commands/tokens/tokens.create.command.js +4 -4
  45. package/src/config/cache.js +60 -0
  46. package/src/config/config.js +233 -0
  47. package/src/config/features.js +167 -0
  48. package/src/config/paths.js +23 -0
  49. package/src/lib/date-utils.js +48 -0
  50. package/src/lib/fs.js +49 -0
  51. package/src/lib/operator-commands.js +4 -4
  52. package/src/lib/profile.js +116 -0
  53. package/src/logger.js +123 -69
  54. package/src/logger.types.d.ts +5 -0
  55. package/src/models/app_configuration.js +42 -39
  56. package/src/models/application_configuration.js +30 -30
  57. package/src/models/config-provider.js +34 -0
  58. package/src/models/git-isomorphic.js +153 -0
  59. package/src/models/git-system.js +182 -0
  60. package/src/models/git.js +150 -128
  61. package/src/models/ids-resolver.js +1 -1
  62. package/src/models/log.js +141 -90
  63. package/src/models/send-to-api.js +58 -33
  64. package/src/models/user.js +0 -11
  65. package/src/models/utils.js +2 -2
  66. package/src/experimental-features.js +0 -91
  67. package/src/lib/format-date.js +0 -3
  68. package/src/models/configuration.js +0 -140
  69. package/src/models/log-v4.js +0 -189
@@ -0,0 +1,153 @@
1
+ import * as git from 'isomorphic-git';
2
+ import _ from 'lodash';
3
+ import fs from 'node:fs';
4
+ import { config } from '../config/config.js';
5
+ import { slugify } from '../lib/slugify.js';
6
+ import { Git } from './git.js';
7
+ import * as http from './isomorphic-http-with-agent.js';
8
+
9
+ export class GitIsomorphic extends Git {
10
+ constructor() {
11
+ super('isomorphic');
12
+ }
13
+
14
+ async #getRepo() {
15
+ const dir = await this._getRepoDir();
16
+ return { fs, dir, http };
17
+ }
18
+
19
+ #onAuth() {
20
+ return {
21
+ username: config.token,
22
+ password: config.secret,
23
+ };
24
+ }
25
+
26
+ async addRemote(remoteName, url) {
27
+ this._debug('addRemote', remoteName, url);
28
+ const repo = await this.#getRepo();
29
+ const safeRemoteName = slugify(remoteName);
30
+ const allRemotes = await git.listRemotes({ ...repo });
31
+ const existingRemote = _.find(allRemotes, { remote: safeRemoteName });
32
+ if (existingRemote == null) {
33
+ // In some situations, we may end up with race conditions so we force it
34
+ return git.addRemote({ ...repo, remote: safeRemoteName, url, force: true });
35
+ }
36
+ }
37
+
38
+ async resolveFullCommitId(commitId) {
39
+ this._debug('resolveFullCommitId', commitId);
40
+ if (commitId == null) {
41
+ return null;
42
+ }
43
+ try {
44
+ const repo = await this.#getRepo();
45
+ return await git.expandOid({ ...repo, oid: commitId });
46
+ } catch (e) {
47
+ if (e.code === 'ShortOidNotFound') {
48
+ throw new Error(`Commit id ${commitId} is ambiguous`);
49
+ }
50
+ throw e;
51
+ }
52
+ }
53
+
54
+ async getRemoteCommit(remoteUrl) {
55
+ this._debug('getRemoteCommit', remoteUrl);
56
+ const repo = await this.#getRepo();
57
+ const remoteInfos = await git.getRemoteInfo({
58
+ ...repo,
59
+ onAuth: this.#onAuth,
60
+ url: remoteUrl,
61
+ });
62
+ return _.get(remoteInfos, 'refs.heads.master');
63
+ }
64
+
65
+ async getFullBranch(branchName) {
66
+ this._debug('getFullBranch', branchName);
67
+ const repo = await this.#getRepo();
68
+ if (branchName === '') {
69
+ const currentBranch = await git.currentBranch({ ...repo, fullname: true });
70
+ return currentBranch || 'HEAD';
71
+ }
72
+ return git.expandRef({ ...repo, ref: branchName });
73
+ }
74
+
75
+ async getBranchCommit(refspec) {
76
+ this._debug('getBranchCommit', refspec);
77
+ const repo = await this.#getRepo();
78
+ const oid = await git.resolveRef({ ...repo, ref: refspec });
79
+ // When a refspec refers to an annotated tag, the OID ref represents the annotation and not the commit directly,
80
+ // that's why we need a call to `readCommit`.
81
+ const res = await git.readCommit({ ...repo, ref: refspec, oid });
82
+ return res.oid;
83
+ }
84
+
85
+ async isExistingTag(tag) {
86
+ this._debug('isExistingTag', tag);
87
+ const repo = await this.#getRepo();
88
+ const tags = await git.listTags({
89
+ ...repo,
90
+ });
91
+ return tags.includes(tag);
92
+ }
93
+
94
+ async push(remoteUrl, branchRefspec, force, remoteName) {
95
+ const refspec = `${branchRefspec}:refs/heads/master`;
96
+ this._debug('push', remoteUrl, refspec, force ? '--force' : '');
97
+ const repo = await this.#getRepo();
98
+ try {
99
+ const push = await git.push({
100
+ ...repo,
101
+ onAuth: this.#onAuth,
102
+ url: remoteUrl,
103
+ ref: branchRefspec,
104
+ remoteRef: 'master',
105
+ remote: remoteName,
106
+ force,
107
+ });
108
+ if (push.errors != null) {
109
+ throw new Error(push.errors.join(', '));
110
+ }
111
+ return push;
112
+ } catch (e) {
113
+ if (e.code === 'PushRejectedNonFastForward') {
114
+ throw new Error('Push rejected because it was not a simple fast-forward, use --force to override');
115
+ }
116
+ throw e;
117
+ }
118
+ }
119
+
120
+ async completeBranches() {
121
+ this._debug('completeBranches');
122
+ return this.#getRepo().then((repo) => git.listBranches(repo));
123
+ }
124
+
125
+ /**
126
+ * Check if the current directory is a git repository
127
+ * @returns {Promise<boolean>}
128
+ */
129
+ async isInsideGitRepo() {
130
+ this._debug('isInsideGitRepo');
131
+ return this.#getRepo()
132
+ .then(() => true)
133
+ .catch(() => false);
134
+ }
135
+
136
+ /**
137
+ * Check if the current git working directory is clean
138
+ * @returns {Promise<boolean>}
139
+ */
140
+ async isGitWorkingDirectoryClean() {
141
+ this._debug('isGitWorkingDirectoryClean');
142
+ const repo = await this.#getRepo();
143
+ const status = await git.statusMatrix({ ...repo });
144
+ const isStatusEmpty =
145
+ status.filter(([filepath, head, workdir]) => {
146
+ // WARNING: isomorphic-git does not support global gitignore so we filter hidden files and dirs to reduce the amount of false positives
147
+ const isHidden = filepath.startsWith('.');
148
+ const isCleverJson = filepath === '.clever.json';
149
+ return (!isHidden || isCleverJson) && head !== workdir;
150
+ }).length === 0;
151
+ return isStatusEmpty;
152
+ }
153
+ }
@@ -0,0 +1,182 @@
1
+ import { simpleGit } from 'simple-git';
2
+ import { config } from '../config/config.js';
3
+ import { slugify } from '../lib/slugify.js';
4
+ import { Git } from './git.js';
5
+
6
+ export class GitSystem extends Git {
7
+ #gitAvailabilityChecked = false;
8
+
9
+ constructor() {
10
+ super('system');
11
+ }
12
+
13
+ async addRemote(remoteName, url) {
14
+ this._debug('addRemote', remoteName, url);
15
+ const git = await this.#getSimpleGit();
16
+ const safeRemoteName = slugify(remoteName);
17
+ const remotes = await git.getRemotes();
18
+ const existingRemote = remotes.find((r) => r.name === safeRemoteName);
19
+ if (existingRemote == null) {
20
+ await git.addRemote(safeRemoteName, url);
21
+ }
22
+ }
23
+
24
+ async #getSimpleGit() {
25
+ await this.#checkGitAvailability();
26
+ const dir = await this._getRepoDir();
27
+ return simpleGit(dir);
28
+ }
29
+
30
+ async #checkGitAvailability() {
31
+ if (this.#gitAvailabilityChecked) {
32
+ return;
33
+ }
34
+ const git = simpleGit();
35
+ const ver = await git.version();
36
+ if (!ver.installed) {
37
+ throw new GitNotFoundError();
38
+ }
39
+ this.#gitAvailabilityChecked = true;
40
+ }
41
+
42
+ async resolveFullCommitId(commitId) {
43
+ this._debug('resolveFullCommitId', commitId);
44
+ if (commitId == null) {
45
+ return null;
46
+ }
47
+ const git = await this.#getSimpleGit();
48
+ try {
49
+ const fullOid = await git.revparse([commitId]);
50
+ return fullOid.trim();
51
+ } catch (e) {
52
+ if (e.message.includes('unknown revision') || e.message.includes('ambiguous argument')) {
53
+ throw new Error(`Commit id ${commitId} is ambiguous`);
54
+ }
55
+ throw e;
56
+ }
57
+ }
58
+
59
+ async getRemoteCommit(remoteUrl) {
60
+ const git = await this.#getSimpleGit();
61
+ const authUrl = this.#buildAuthenticatedUrl(remoteUrl);
62
+ this._debug('getRemoteCommit', this.#redactUrl(authUrl));
63
+ try {
64
+ const result = await git.listRemote(['--refs', authUrl.toString()]);
65
+ // Parse output: "<sha>\trefs/heads/master"
66
+ const lines = result.trim().split('\n');
67
+ for (const line of lines) {
68
+ const [sha, ref] = line.split('\t');
69
+ if (ref === 'refs/heads/master') {
70
+ return sha;
71
+ }
72
+ }
73
+ return undefined;
74
+ } catch {
75
+ return undefined;
76
+ }
77
+ }
78
+
79
+ #buildAuthenticatedUrl(url) {
80
+ const urlObj = new URL(url);
81
+ urlObj.username = config.token;
82
+ urlObj.password = config.secret;
83
+ return urlObj;
84
+ }
85
+
86
+ async getFullBranch(branchName) {
87
+ this._debug('getFullBranch', branchName);
88
+ const git = await this.#getSimpleGit();
89
+ if (branchName === '') {
90
+ const branch = await git.branch();
91
+ if (branch.current) {
92
+ return `refs/heads/${branch.current}`;
93
+ }
94
+ return 'HEAD';
95
+ }
96
+ // Try to expand the ref
97
+ try {
98
+ const fullRef = await git.revparse(['--symbolic-full-name', branchName]);
99
+ return fullRef.trim();
100
+ } catch {
101
+ // If it fails, it might be a commit hash, return as-is
102
+ return branchName;
103
+ }
104
+ }
105
+
106
+ async getBranchCommit(refspec) {
107
+ this._debug('getBranchCommit', refspec);
108
+ const git = await this.#getSimpleGit();
109
+ // Use rev-parse with ^{commit} to dereference tags to their commit
110
+ const oid = await git.revparse([`${refspec}^{commit}`]);
111
+ return oid.trim();
112
+ }
113
+
114
+ async isExistingTag(tag) {
115
+ this._debug('isExistingTag', tag);
116
+ const git = await this.#getSimpleGit();
117
+ const tags = await git.tags();
118
+ return tags.all.includes(tag);
119
+ }
120
+
121
+ #redactUrl(url) {
122
+ const urlObj = typeof url === 'string' ? new URL(url) : url;
123
+ const redacted = new URL(urlObj.toString());
124
+ if (redacted.username) redacted.username = '***';
125
+ if (redacted.password) redacted.password = '***';
126
+ return redacted.toString();
127
+ }
128
+
129
+ async push(remoteUrl, branchRefspec, force) {
130
+ const git = await this.#getSimpleGit();
131
+ const authUrl = this.#buildAuthenticatedUrl(remoteUrl);
132
+ const refspec = `${branchRefspec}:refs/heads/master`;
133
+ this._debug('push', this.#redactUrl(authUrl), refspec, force ? '--force' : '');
134
+ const options = ['--porcelain'];
135
+ if (force) {
136
+ options.push('--force');
137
+ }
138
+ try {
139
+ await git.push(authUrl.toString(), refspec, options);
140
+ return {};
141
+ } catch (e) {
142
+ if (e.message.includes('non-fast-forward') || e.message.includes('[rejected]')) {
143
+ throw new Error('Push rejected because it was not a simple fast-forward, use --force to override');
144
+ }
145
+ throw e;
146
+ }
147
+ }
148
+
149
+ async completeBranches() {
150
+ this._debug('completeBranches');
151
+ const git = await this.#getSimpleGit();
152
+ const branches = await git.branchLocal();
153
+ return branches.all;
154
+ }
155
+
156
+ async isInsideGitRepo() {
157
+ this._debug('isInsideGitRepo');
158
+ try {
159
+ await this._getRepoDir();
160
+ return true;
161
+ } catch {
162
+ return false;
163
+ }
164
+ }
165
+
166
+ async isGitWorkingDirectoryClean() {
167
+ this._debug('isGitWorkingDirectoryClean');
168
+ const git = await this.#getSimpleGit();
169
+ const status = await git.status();
170
+ return status.isClean();
171
+ }
172
+ }
173
+
174
+ class GitNotFoundError extends Error {
175
+ constructor() {
176
+ super(
177
+ 'The system git feature requires git to be installed and available in your PATH\n' +
178
+ 'Either install git or disable this feature with: clever features disable system-git',
179
+ );
180
+ this.name = 'GitNotFoundError';
181
+ }
182
+ }
package/src/models/git.js CHANGED
@@ -1,151 +1,173 @@
1
- import * as git from 'isomorphic-git';
2
- import _ from 'lodash';
3
1
  import fs from 'node:fs';
4
2
  import path from 'node:path';
5
- import { slugify } from '../lib/slugify.js';
6
- import { loadOAuthConf } from './configuration.js';
3
+
4
+ import { isFeatureEnabled } from '../config/features.js';
5
+ import { Logger } from '../logger.js';
7
6
  import { findPath } from './fs-utils.js';
8
- import * as http from './isomorphic-http-with-agent.js';
9
-
10
- async function getRepo() {
11
- try {
12
- const dir = await findPath('.', '.git');
13
- return { fs, dir, http };
14
- } catch {
15
- throw new Error('Could not find the .git folder.');
16
- }
17
- }
18
7
 
19
- async function onAuth() {
20
- const tokens = await loadOAuthConf();
21
- return {
22
- username: tokens.token,
23
- password: tokens.secret,
24
- };
25
- }
8
+ /**
9
+ * Abstract base class for git operations.
10
+ * Implementations: GitIsomorphic (JS-based) and GitSystem (system git command)
11
+ */
12
+ export class Git {
13
+ /** @type {Git | null} */
14
+ static #instance = null;
15
+
16
+ /** @type {string} */
17
+ #name;
26
18
 
27
- export async function addRemote(remoteName, url) {
28
- const repo = await getRepo();
29
- const safeRemoteName = slugify(remoteName);
30
- const allRemotes = await git.listRemotes({ ...repo });
31
- const existingRemote = _.find(allRemotes, { remote: safeRemoteName });
32
- if (existingRemote == null) {
33
- // In some situations, we may end up with race conditions so we force it
34
- return git.addRemote({ ...repo, remote: safeRemoteName, url, force: true });
19
+ /**
20
+ * @param {string} name - Backend name for logging purposes
21
+ */
22
+ constructor(name) {
23
+ this.#name = name;
35
24
  }
36
- }
37
25
 
38
- export async function resolveFullCommitId(commitId) {
39
- if (commitId == null) {
40
- return null;
26
+ /**
27
+ * Get the git implementation based on the feature flag.
28
+ * Returns a cached instance if available.
29
+ * @returns {Promise<Git>}
30
+ */
31
+ static async get() {
32
+ if (Git.#instance == null) {
33
+ const useSystemGit = await isFeatureEnabled('system-git');
34
+ if (useSystemGit) {
35
+ const { GitSystem } = await import('./git-system.js');
36
+ Git.#instance = new GitSystem();
37
+ } else {
38
+ const { GitIsomorphic } = await import('./git-isomorphic.js');
39
+ Git.#instance = new GitIsomorphic();
40
+ }
41
+ }
42
+ return Git.#instance;
41
43
  }
42
- try {
43
- const repo = await getRepo();
44
- return await git.expandOid({ ...repo, oid: commitId });
45
- } catch (e) {
46
- if (e.code === 'ShortOidNotFound') {
47
- throw new Error(`Commit id ${commitId} is ambiguous`);
44
+
45
+ /**
46
+ * Log a debug message for git operations
47
+ * @protected
48
+ * @param {string} operation - The operation name
49
+ * @param {...string} args - Additional arguments to log
50
+ */
51
+ _debug(operation, ...args) {
52
+ const argsStr = args.length > 0 ? ` ${args.join(' ')}` : '';
53
+ Logger.debug(`git(${this.#name}): ${operation}${argsStr}`);
54
+ }
55
+
56
+ /**
57
+ * Get the repository directory
58
+ * @protected
59
+ * @returns {Promise<string>}
60
+ */
61
+ async _getRepoDir() {
62
+ try {
63
+ return await findPath('.', '.git');
64
+ } catch {
65
+ throw new Error('Could not find the .git folder');
48
66
  }
49
- throw e;
50
67
  }
51
- }
52
68
 
53
- export async function getRemoteCommit(remoteUrl) {
54
- const repo = await getRepo();
55
- const remoteInfos = await git.getRemoteInfo({
56
- ...repo,
57
- onAuth,
58
- url: remoteUrl,
59
- });
60
- return _.get(remoteInfos, 'refs.heads.master');
61
- }
69
+ /**
70
+ * Add a remote to the repository
71
+ * @param {string} remoteName
72
+ * @param {string} url
73
+ * @returns {Promise<void>}
74
+ */
75
+ async addRemote(remoteName, url) {
76
+ throw new Error('Not implemented');
77
+ }
62
78
 
63
- export async function getFullBranch(branchName) {
64
- const repo = await getRepo();
65
- if (branchName === '') {
66
- const currentBranch = await git.currentBranch({ ...repo, fullname: true });
67
- return currentBranch || 'HEAD';
79
+ /**
80
+ * Resolve a short commit ID to its full form
81
+ * @param {string | null} commitId
82
+ * @returns {Promise<string | null>}
83
+ */
84
+ async resolveFullCommitId(commitId) {
85
+ throw new Error('Not implemented');
68
86
  }
69
- return git.expandRef({ ...repo, ref: branchName });
70
- }
71
87
 
72
- export async function getBranchCommit(refspec) {
73
- const repo = await getRepo();
74
- const oid = await git.resolveRef({ ...repo, ref: refspec });
75
- // When a refspec refers to an annotated tag, the OID ref represents the annotation and not the commit directly,
76
- // that's why we need a call to `readCommit`.
77
- const res = await git.readCommit({ ...repo, ref: refspec, oid });
78
- return res.oid;
79
- }
88
+ /**
89
+ * Get the commit SHA of the master branch on a remote
90
+ * @param {string} remoteUrl
91
+ * @returns {Promise<string | undefined>}
92
+ */
93
+ async getRemoteCommit(remoteUrl) {
94
+ throw new Error('Not implemented');
95
+ }
80
96
 
81
- export async function isExistingTag(tag) {
82
- const repo = await getRepo();
83
- const tags = await git.listTags({
84
- ...repo,
85
- });
86
- return tags.includes(tag);
87
- }
97
+ /**
98
+ * Get the full ref name for a branch
99
+ * @param {string} branchName - Branch name, or empty string for current branch
100
+ * @returns {Promise<string>}
101
+ */
102
+ async getFullBranch(branchName) {
103
+ throw new Error('Not implemented');
104
+ }
88
105
 
89
- export async function push(remoteUrl, branchRefspec, force) {
90
- const repo = await getRepo();
91
- try {
92
- const push = await git.push({
93
- ...repo,
94
- onAuth,
95
- url: remoteUrl,
96
- ref: branchRefspec,
97
- remoteRef: 'master',
98
- force,
99
- });
100
- if (push.errors != null) {
101
- throw new Error(push.errors.join(', '));
102
- }
103
- return push;
104
- } catch (e) {
105
- if (e.code === 'PushRejectedNonFastForward') {
106
- throw new Error('Push rejected because it was not a simple fast-forward. Use "--force" to override.');
107
- }
108
- throw e;
106
+ /**
107
+ * Get the commit SHA for a branch or tag
108
+ * @param {string} refspec
109
+ * @returns {Promise<string>}
110
+ */
111
+ async getBranchCommit(refspec) {
112
+ throw new Error('Not implemented');
109
113
  }
110
- }
111
114
 
112
- export function completeBranches() {
113
- return getRepo().then((repo) => git.listBranches(repo));
114
- }
115
+ /**
116
+ * Check if a tag exists
117
+ * @param {string} tag
118
+ * @returns {Promise<boolean>}
119
+ */
120
+ async isExistingTag(tag) {
121
+ throw new Error('Not implemented');
122
+ }
115
123
 
116
- export async function isShallow() {
117
- const { dir } = await getRepo();
118
- try {
119
- await fs.promises.access(path.join(dir, '.git', 'shallow'));
120
- return true;
121
- } catch {
122
- return false;
124
+ /**
125
+ * Push to a remote repository
126
+ * @param {string} remoteUrl
127
+ * @param {string} branchRefspec
128
+ * @param {boolean} force
129
+ * @returns {Promise<object>}
130
+ */
131
+ async push(remoteUrl, branchRefspec, force) {
132
+ throw new Error('Not implemented');
123
133
  }
124
- }
125
134
 
126
- /**
127
- * Check if the current directory is a git repository
128
- * @returns {Promise<boolean>}
129
- */
130
- export async function isInsideGitRepo() {
131
- return getRepo()
132
- .then(() => true)
133
- .catch(() => false);
134
- }
135
+ /**
136
+ * List local branches (for autocompletion)
137
+ * @returns {Promise<string[]>}
138
+ */
139
+ async completeBranches() {
140
+ throw new Error('Not implemented');
141
+ }
135
142
 
136
- /**
137
- * Check if the current git working directory is clean
138
- * @returns {Promise<boolean>}
139
- */
140
- export async function isGitWorkingDirectoryClean() {
141
- const repo = await getRepo();
142
- const status = await git.statusMatrix({ ...repo });
143
- const isStatusEmpty =
144
- status.filter(([filepath, head, workdir]) => {
145
- // WARNING: isomorphic-git does not support global gitignore so we filter hidden files and dirs to reduce the amount of false positives
146
- const isHidden = filepath.startsWith('.');
147
- const isCleverJson = filepath === '.clever.json';
148
- return (!isHidden || isCleverJson) && head !== workdir;
149
- }).length === 0;
150
- return isStatusEmpty;
143
+ /**
144
+ * Check if the repository is a shallow clone
145
+ * @returns {Promise<boolean>}
146
+ */
147
+ async isShallow() {
148
+ this._debug('isShallow');
149
+ const dir = await this._getRepoDir();
150
+ try {
151
+ await fs.promises.access(path.join(dir, '.git', 'shallow'));
152
+ return true;
153
+ } catch {
154
+ return false;
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Check if the current directory is inside a git repository
160
+ * @returns {Promise<boolean>}
161
+ */
162
+ async isInsideGitRepo() {
163
+ throw new Error('Not implemented');
164
+ }
165
+
166
+ /**
167
+ * Check if the git working directory is clean (no uncommitted changes)
168
+ * @returns {Promise<boolean>}
169
+ */
170
+ async isGitWorkingDirectoryClean() {
171
+ throw new Error('Not implemented');
172
+ }
151
173
  }
@@ -1,7 +1,7 @@
1
1
  import { getSummary } from '@clevercloud/client/esm/api/v2/user.js';
2
+ import { loadIdsCache, writeIdsCache } from '../config/cache.js';
2
3
  import { Logger } from '../logger.js';
3
4
  import * as User from '../models/user.js';
4
- import { loadIdsCache, writeIdsCache } from './configuration.js';
5
5
  import * as Organisation from './organisation.js';
6
6
  import { sendToApi } from './send-to-api.js';
7
7