netlify 27.16.1 → 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
|
-
|
|
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
|
|
179
|
-
if (
|
|
180
|
-
logExistingAndExit({ siteInfo:
|
|
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 =
|
|
261
|
+
const siteInfo = linkedSiteInfo ?? (await createOrLinkSiteToRepo(command));
|
|
195
262
|
log();
|
|
196
263
|
// Check for existing CI setup
|
|
197
264
|
const remoteBuildRepo = getRepoUrl(siteInfo);
|
|
@@ -57,6 +57,7 @@ export const debugFetch = async (url, init) => {
|
|
|
57
57
|
};
|
|
58
58
|
export const fetchHistoricalLogs = async ({ baseUrl, accessToken, from, to, deployId, }) => {
|
|
59
59
|
const entries = [];
|
|
60
|
+
const requestedCursors = new Set();
|
|
60
61
|
let cursor;
|
|
61
62
|
do {
|
|
62
63
|
const parsedUrl = new URL(baseUrl);
|
|
@@ -79,7 +80,13 @@ export const fetchHistoricalLogs = async ({ baseUrl, accessToken, from, to, depl
|
|
|
79
80
|
}
|
|
80
81
|
const body = (await response.json());
|
|
81
82
|
entries.push(...(body.logs ?? []));
|
|
82
|
-
cursor = body.
|
|
83
|
+
cursor = body.next;
|
|
84
|
+
if (cursor) {
|
|
85
|
+
if (requestedCursors.has(cursor)) {
|
|
86
|
+
throw new Error(`Failed to fetch logs: pagination repeated cursor ${cursor}`);
|
|
87
|
+
}
|
|
88
|
+
requestedCursors.add(cursor);
|
|
89
|
+
}
|
|
83
90
|
} while (cursor);
|
|
84
91
|
return entries;
|
|
85
92
|
};
|
|
@@ -258,7 +258,9 @@ const runHistoricalMode = async ({ sources, client, siteId, accessToken, deployI
|
|
|
258
258
|
to,
|
|
259
259
|
deployId,
|
|
260
260
|
});
|
|
261
|
-
|
|
261
|
+
for (const entry of entries) {
|
|
262
|
+
allEntries.push(entry);
|
|
263
|
+
}
|
|
262
264
|
}
|
|
263
265
|
}
|
|
264
266
|
if (sources.includes('edge-functions')) {
|
|
@@ -270,7 +272,9 @@ const runHistoricalMode = async ({ sources, client, siteId, accessToken, deployI
|
|
|
270
272
|
deployId,
|
|
271
273
|
filterNames: edgeFunctionNames,
|
|
272
274
|
});
|
|
273
|
-
|
|
275
|
+
for (const entry of entries) {
|
|
276
|
+
allEntries.push(entry);
|
|
277
|
+
}
|
|
274
278
|
}
|
|
275
279
|
let serverLogsTruncated = false;
|
|
276
280
|
if (sources.includes('server')) {
|
|
@@ -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
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
57
|
+
"@netlify/api": "^15.1.6",
|
|
58
58
|
"@netlify/blobs": "^11.1.7",
|
|
59
|
-
"@netlify/build": "^37.7.
|
|
59
|
+
"@netlify/build": "^37.7.6",
|
|
60
60
|
"@netlify/build-info": "^11.3.1",
|
|
61
|
-
"@netlify/config": "^25.
|
|
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",
|