netlify 27.16.2 → 27.16.3

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.
@@ -66,7 +66,7 @@ export default class BaseCommand extends Command {
66
66
  /** Will be called on the end of an action to track the metrics */
67
67
  onEnd(error_?: unknown): Promise<undefined>;
68
68
  authenticate(tokenFromFlag?: string): Promise<string>;
69
- private refreshAccounts;
69
+ refreshAccounts(): Promise<void>;
70
70
  expensivelyAuthenticate(): Promise<string>;
71
71
  /** Adds some data to the analytics payload */
72
72
  setAnalyticsPayload(payload: Record<string, unknown>): void;
@@ -1,6 +1,6 @@
1
1
  import { confirm, select } from '@inquirer/prompts';
2
2
  import { isEmpty } from '../../utils/object-utilities.js';
3
- import { chalk, exit, log, netlifyCommand } from '../../utils/command-helpers.js';
3
+ import { chalk, exit, log, logAndThrowError, netlifyCommand, warn } from '../../utils/command-helpers.js';
4
4
  import getRepoData from '../../utils/get-repo-data.js';
5
5
  import { ensureNetlifyIgnore } from '../../utils/gitignore.js';
6
6
  import { configureRepo } from '../../utils/init/config.js';
@@ -30,6 +30,71 @@ const logExistingAndExit = ({ siteInfo }) => {
30
30
  log(`2. Then run ${chalk.cyanBright.bold(`${netlifyCommand()} init`)} again`);
31
31
  return exit();
32
32
  };
33
+ const logLinkedWithoutGitAndExit = ({ siteInfo, repoError, }) => {
34
+ if (repoError.reason !== 'no-remote') {
35
+ warn(`Could not read the git remote: ${repoError.error}`);
36
+ log(`Fix the git remote, then run ${chalk.cyanBright.bold(`${netlifyCommand()} init`)} again to connect it.`);
37
+ }
38
+ log();
39
+ log(`This folder is already linked to the Netlify project ${chalk.cyan(siteInfo.name)}`);
40
+ log();
41
+ log(`Project Url: ${chalk.cyan(siteInfo.ssl_url || siteInfo.url)}`);
42
+ log();
43
+ log(`To deploy to this project, run ${chalk.cyanBright.bold(`${netlifyCommand()} deploy`)}.`);
44
+ log(`To link this folder to a different project, run ${chalk.cyanBright.bold(`${netlifyCommand()} unlink`)}, then ${chalk.cyanBright.bold(`${netlifyCommand()} init`)}.`);
45
+ return exit();
46
+ };
47
+ const ignoreLinkedSiteForThisRun = (command) => {
48
+ const { site } = command.netlify;
49
+ command.siteId = undefined;
50
+ command.netlify.siteInfo = {};
51
+ command.netlify.site = {
52
+ root: site.root,
53
+ configPath: site.configPath,
54
+ get id() {
55
+ return command.siteId;
56
+ },
57
+ set id(id) {
58
+ site.id = id;
59
+ },
60
+ };
61
+ };
62
+ const getLinkedSiteInfo = async (command) => {
63
+ const { api, siteInfo } = command.netlify;
64
+ if (isEmpty(siteInfo)) {
65
+ return undefined;
66
+ }
67
+ if (siteInfo.name) {
68
+ return siteInfo;
69
+ }
70
+ try {
71
+ return (await api.getSite({ siteId: siteInfo.id }));
72
+ }
73
+ catch (error_) {
74
+ const { status, message } = error_;
75
+ const unusableLinks = {
76
+ 403: {
77
+ reason: 'forbidden',
78
+ warning: `You don't have access to the Netlify project ${siteInfo.id} linked to this folder. Continuing without it.`,
79
+ },
80
+ 404: {
81
+ reason: 'not_found',
82
+ warning: `The Netlify project ${siteInfo.id} linked to this folder no longer exists. Continuing without it.`,
83
+ },
84
+ };
85
+ const unusableLink = unusableLinks[status];
86
+ if (unusableLink === undefined) {
87
+ return logAndThrowError(`Could not load the Netlify project ${siteInfo.id} linked to this folder (${status ? `status ${status.toString()}` : message}). Try again, or run ${chalk.cyanBright.bold(`${netlifyCommand()} unlink`)} to start over.`);
88
+ }
89
+ warn(unusableLink.warning);
90
+ await track('sites_unlinked', { siteId: siteInfo.id, kind: 'init', reason: unusableLink.reason });
91
+ ignoreLinkedSiteForThisRun(command);
92
+ if (command.netlify.accounts.length === 0) {
93
+ await command.refreshAccounts();
94
+ }
95
+ return undefined;
96
+ }
97
+ };
33
98
  /**
34
99
  * Creates and new site and exits the process
35
100
  */
@@ -167,7 +232,6 @@ export const init = async (options, command, { customizeExitMessage, exitAfterCo
167
232
  resetContext: options.resetContext,
168
233
  });
169
234
  const { repositoryRoot, state } = command.netlify;
170
- const { siteInfo: existingSiteInfo } = command.netlify;
171
235
  // Check logged in status
172
236
  await command.authenticate();
173
237
  // Add .netlify to .gitignore file
@@ -175,13 +239,16 @@ export const init = async (options, command, { customizeExitMessage, exitAfterCo
175
239
  if (shouldSetupAgentSkills) {
176
240
  await installAgentSkills(command, resetContext);
177
241
  }
178
- const repoUrl = getRepoUrl(existingSiteInfo);
179
- if (repoUrl && !options.force) {
180
- logExistingAndExit({ siteInfo: existingSiteInfo });
242
+ const linkedSiteInfo = await getLinkedSiteInfo(command);
243
+ if (linkedSiteInfo && getRepoUrl(linkedSiteInfo) && !options.force) {
244
+ logExistingAndExit({ siteInfo: linkedSiteInfo });
181
245
  }
182
246
  // Look for local repo
183
247
  const repoData = await getRepoData({ workingDir: command.workingDir, remoteName: options.gitRemoteName });
184
248
  if ('error' in repoData) {
249
+ if (linkedSiteInfo) {
250
+ return logLinkedWithoutGitAndExit({ siteInfo: linkedSiteInfo, repoError: repoData });
251
+ }
185
252
  // TODO(ndhoule): Custom error messaage here
186
253
  return await handleNoGitRemoteAndExit({
187
254
  command,
@@ -191,7 +258,7 @@ export const init = async (options, command, { customizeExitMessage, exitAfterCo
191
258
  customizeExitMessage,
192
259
  });
193
260
  }
194
- const siteInfo = isEmpty(existingSiteInfo) ? await createOrLinkSiteToRepo(command) : existingSiteInfo;
261
+ const siteInfo = linkedSiteInfo ?? (await createOrLinkSiteToRepo(command));
195
262
  log();
196
263
  // Check for existing CI setup
197
264
  const remoteBuildRepo = getRepoUrl(siteInfo);
@@ -7,11 +7,14 @@ export interface RepoData {
7
7
  provider: string | null;
8
8
  httpsUrl: string;
9
9
  }
10
+ export type RepoDataErrorReason = 'no-remote' | 'unknown-remote' | 'invalid-url' | 'unreadable';
11
+ export interface RepoDataError {
12
+ error: string;
13
+ reason: RepoDataErrorReason;
14
+ }
10
15
  declare const getRepoData: ({ remoteName, workingDir, }: {
11
16
  remoteName?: string;
12
17
  workingDir: string;
13
- }) => Promise<RepoData | {
14
- error: string;
15
- }>;
18
+ }) => Promise<RepoData | RepoDataError>;
16
19
  export default getRepoData;
17
20
  //# sourceMappingURL=get-repo-data.d.ts.map
@@ -5,14 +5,23 @@ import gitRepoInfo from 'git-repo-info';
5
5
  import gitconfiglocal from 'gitconfiglocal';
6
6
  import parseGithubUrl from 'parse-github-url';
7
7
  import { log } from './command-helpers.js';
8
+ class RepoDataLookupError extends Error {
9
+ reason;
10
+ constructor(message, reason) {
11
+ super(message);
12
+ this.reason = reason;
13
+ }
14
+ }
15
+ const redactCredentials = (url) => url.replace(/\/\/[^/@]*@/, '//***@');
8
16
  const getRepoData = async ({ remoteName, workingDir, }) => {
9
17
  try {
10
- const [gitConfig, gitDirectory] = await Promise.all([
11
- util.promisify(gitconfiglocal)(workingDir),
12
- findUp('.git', { cwd: workingDir, type: 'directory' }),
13
- ]);
14
- if (!gitDirectory || !gitConfig?.remote || Object.keys(gitConfig.remote).length === 0) {
15
- throw new Error('No Git remote found');
18
+ const gitDirectory = await findUp('.git', { cwd: workingDir, type: 'directory' });
19
+ if (!gitDirectory) {
20
+ throw new RepoDataLookupError('No Git remote found', 'no-remote');
21
+ }
22
+ const gitConfig = await util.promisify(gitconfiglocal)(workingDir);
23
+ if (!gitConfig?.remote || Object.keys(gitConfig.remote).length === 0) {
24
+ throw new RepoDataLookupError('No Git remote found', 'no-remote');
16
25
  }
17
26
  const baseGitPath = dirname(gitDirectory);
18
27
  if (workingDir !== baseGitPath) {
@@ -25,13 +34,13 @@ const getRepoData = async ({ remoteName, workingDir, }) => {
25
34
  if (!Object.prototype.hasOwnProperty.call(gitConfig.remote, remoteName) ||
26
35
  !gitConfig.remote[remoteName] ||
27
36
  Object.keys(gitConfig.remote[remoteName]).length === 0) {
28
- throw new Error(`The specified remote "${remoteName}" is not defined in Git repo. Please use --git-remote-name flag to specify a remote.`);
37
+ throw new RepoDataLookupError(`The specified remote "${remoteName}" is not defined in Git repo. Please use --git-remote-name flag to specify a remote.`, 'unknown-remote');
29
38
  }
30
39
  const { url } = gitConfig.remote[remoteName];
31
40
  const parsedUrl = parseGithubUrl(url);
32
41
  // TODO(serhalp): Validate more aggressively? We should probably require `owner`, `repo`, `host`?
33
42
  if (parsedUrl == null) {
34
- throw new Error(`The specified Git remote ${remoteName} is not a valid URL: ${url}`);
43
+ throw new RepoDataLookupError(`The specified Git remote ${remoteName} is not a valid URL: ${redactCredentials(String(url))}`, 'invalid-url');
35
44
  }
36
45
  const { host, name, owner, repo } = parsedUrl;
37
46
  const { branch } = gitRepoInfo();
@@ -48,6 +57,7 @@ const getRepoData = async ({ remoteName, workingDir, }) => {
48
57
  catch (error) {
49
58
  return {
50
59
  error: error instanceof Error ? error.message : (error?.toString() ?? 'Failed to get repo data'),
60
+ reason: error instanceof RepoDataLookupError ? error.reason : 'unreadable',
51
61
  };
52
62
  }
53
63
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "netlify",
3
- "version": "27.16.2",
3
+ "version": "27.16.3",
4
4
  "description": "Netlify command line tool",
5
5
  "keywords": [
6
6
  "api",
@@ -54,11 +54,11 @@
54
54
  "@fastify/static": "^10.0.0",
55
55
  "@inquirer/prompts": "^8.7.2",
56
56
  "@netlify/ai": "^1.0.2",
57
- "@netlify/api": "^15.1.5",
57
+ "@netlify/api": "^15.1.6",
58
58
  "@netlify/blobs": "^11.1.7",
59
- "@netlify/build": "^37.7.2",
59
+ "@netlify/build": "^37.7.6",
60
60
  "@netlify/build-info": "^11.3.1",
61
- "@netlify/config": "^25.5.1",
61
+ "@netlify/config": "^25.6.2",
62
62
  "@netlify/dev": "^5.1.12",
63
63
  "@netlify/dev-utils": "^6.0.4",
64
64
  "@netlify/edge-bundler": "^16.2.0",