@netlify/build 35.11.2 → 35.13.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/lib/core/constants.d.ts +3 -3
- package/lib/core/constants.js +3 -3
- package/lib/plugins_core/db_setup/index.js +14 -8
- package/lib/plugins_core/db_setup/migrations.js +6 -5
- package/lib/plugins_core/db_setup/utils.d.ts +10 -0
- package/lib/plugins_core/db_setup/utils.js +21 -0
- package/lib/plugins_core/db_setup/validation.d.ts +8 -1
- package/lib/plugins_core/db_setup/validation.js +27 -6
- package/lib/plugins_core/db_setup/validation.test.js +77 -1
- package/lib/plugins_core/secrets_scanning/utils.js +7 -11
- package/lib/types/config/netlify_config.d.ts +4 -4
- package/package.json +4 -3
package/lib/core/constants.d.ts
CHANGED
|
@@ -48,7 +48,7 @@ export interface NetlifyPluginConstants {
|
|
|
48
48
|
EDGE_FUNCTIONS_SRC?: string;
|
|
49
49
|
/**
|
|
50
50
|
* the directory where database migration source files live.
|
|
51
|
-
* `undefined` if no `netlify/
|
|
51
|
+
* `undefined` if no `netlify/database/migrations` directory exists and if not specified by the user.
|
|
52
52
|
*/
|
|
53
53
|
DB_MIGRATIONS_SRC?: string;
|
|
54
54
|
/**
|
|
@@ -100,7 +100,7 @@ export declare const getConstants: ({ configPath, buildDir, packagePath, functio
|
|
|
100
100
|
token: any;
|
|
101
101
|
mode: any;
|
|
102
102
|
}) => Promise<NetlifyPluginConstants>;
|
|
103
|
-
export declare const addMutableConstants: ({ constants, buildDir, netlifyConfig: { build: { publish, edge_functions: edgeFunctions }, functionsDirectory,
|
|
103
|
+
export declare const addMutableConstants: ({ constants, buildDir, netlifyConfig: { build: { publish, edge_functions: edgeFunctions }, functionsDirectory, database, }, }: {
|
|
104
104
|
constants: any;
|
|
105
105
|
buildDir: any;
|
|
106
106
|
netlifyConfig: {
|
|
@@ -109,7 +109,7 @@ export declare const addMutableConstants: ({ constants, buildDir, netlifyConfig:
|
|
|
109
109
|
edge_functions: any;
|
|
110
110
|
};
|
|
111
111
|
functionsDirectory: any;
|
|
112
|
-
|
|
112
|
+
database: any;
|
|
113
113
|
};
|
|
114
114
|
}) => Promise<{
|
|
115
115
|
[k: string]: string | boolean | undefined;
|
package/lib/core/constants.js
CHANGED
|
@@ -52,7 +52,7 @@ const DB_MIGRATIONS_DIST = '.netlify/internal/db/migrations';
|
|
|
52
52
|
// Retrieve constants which might change during the build if a plugin modifies
|
|
53
53
|
// `netlifyConfig` or creates some default directories.
|
|
54
54
|
// Unlike readonly constants, this is called again before each build step.
|
|
55
|
-
export const addMutableConstants = async function ({ constants, buildDir, netlifyConfig: { build: { publish, edge_functions: edgeFunctions }, functionsDirectory,
|
|
55
|
+
export const addMutableConstants = async function ({ constants, buildDir, netlifyConfig: { build: { publish, edge_functions: edgeFunctions }, functionsDirectory, database, }, }) {
|
|
56
56
|
const constantsA = {
|
|
57
57
|
...constants,
|
|
58
58
|
// Directory that contains the deploy-ready HTML files and assets generated by the build
|
|
@@ -62,7 +62,7 @@ export const addMutableConstants = async function ({ constants, buildDir, netlif
|
|
|
62
62
|
// The directory where Edge Functions source code lives
|
|
63
63
|
EDGE_FUNCTIONS_SRC: edgeFunctions,
|
|
64
64
|
// The directory where database migration source files live
|
|
65
|
-
DB_MIGRATIONS_SRC:
|
|
65
|
+
DB_MIGRATIONS_SRC: database?.migrations?.path,
|
|
66
66
|
};
|
|
67
67
|
const constantsB = await addDefaultConstants(constantsA, buildDir);
|
|
68
68
|
const constantsC = normalizeConstantsPaths(constantsB, buildDir);
|
|
@@ -84,7 +84,7 @@ const DEFAULT_PATHS = [
|
|
|
84
84
|
{ constantName: 'FUNCTIONS_SRC', defaultPath: 'netlify-automatic-functions' },
|
|
85
85
|
{ constantName: 'FUNCTIONS_SRC', defaultPath: 'netlify/functions' },
|
|
86
86
|
{ constantName: 'EDGE_FUNCTIONS_SRC', defaultPath: 'netlify/edge-functions' },
|
|
87
|
-
{ constantName: 'DB_MIGRATIONS_SRC', defaultPath: 'netlify/
|
|
87
|
+
{ constantName: 'DB_MIGRATIONS_SRC', defaultPath: 'netlify/database/migrations' },
|
|
88
88
|
];
|
|
89
89
|
const addDefaultConstant = async function ({ constants, constantName, defaultPath, buildDir }) {
|
|
90
90
|
// Configuration paths are relative to the build directory.
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
2
|
import { logDbProvisioning, logDbMigrations } from '../../log/messages/core_steps.js';
|
|
3
3
|
import { getPackageJson } from '../../utils/package.js';
|
|
4
|
-
import { readMigrationEntries, getMigrationNames } from './utils.js';
|
|
5
|
-
const NPM_PACKAGE_NAME = '@netlify/
|
|
4
|
+
import { readMigrationEntries, getMigrationNames, getMigrationsSrc } from './utils.js';
|
|
5
|
+
const NPM_PACKAGE_NAME = '@netlify/database';
|
|
6
|
+
// TODO: Remove once we stop supporting the legacy `@netlify/db` package name.
|
|
7
|
+
const NPM_PACKAGE_NAME_LEGACY = '@netlify/db';
|
|
6
8
|
const condition = async ({ buildDir, packagePath, featureFlags }) => {
|
|
7
9
|
if (!featureFlags?.netlify_build_db_setup) {
|
|
8
10
|
return false;
|
|
@@ -22,10 +24,11 @@ const condition = async ({ buildDir, packagePath, featureFlags }) => {
|
|
|
22
24
|
const coreStep = async ({ api, branch, buildDir, constants, context, logs }) => {
|
|
23
25
|
const siteId = constants.SITE_ID;
|
|
24
26
|
logDbProvisioning({ logs, branch, context });
|
|
25
|
-
const
|
|
27
|
+
const migrationsSrc = await getMigrationsSrc(buildDir, constants.DB_MIGRATIONS_SRC);
|
|
28
|
+
const entries = await readMigrationEntries(buildDir, migrationsSrc);
|
|
26
29
|
const migrationNames = getMigrationNames(entries);
|
|
27
|
-
if (migrationNames.length > 0) {
|
|
28
|
-
logDbMigrations({ logs, migrations: migrationNames, srcDir:
|
|
30
|
+
if (migrationNames.length > 0 && migrationsSrc) {
|
|
31
|
+
logDbMigrations({ logs, migrations: migrationNames, srcDir: migrationsSrc });
|
|
29
32
|
}
|
|
30
33
|
// @ts-expect-error This is an internal method for now so it isn't typed yet.
|
|
31
34
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
@@ -45,13 +48,16 @@ const coreStep = async ({ api, branch, buildDir, constants, context, logs }) =>
|
|
|
45
48
|
};
|
|
46
49
|
const hasDBPackage = (packageJSON) => {
|
|
47
50
|
const { dependencies = {}, devDependencies = {} } = packageJSON;
|
|
48
|
-
return NPM_PACKAGE_NAME in dependencies ||
|
|
51
|
+
return (NPM_PACKAGE_NAME in dependencies ||
|
|
52
|
+
NPM_PACKAGE_NAME in devDependencies ||
|
|
53
|
+
NPM_PACKAGE_NAME_LEGACY in dependencies ||
|
|
54
|
+
NPM_PACKAGE_NAME_LEGACY in devDependencies);
|
|
49
55
|
};
|
|
50
56
|
export const dbSetup = {
|
|
51
57
|
event: 'onPreBuild',
|
|
52
58
|
coreStep,
|
|
53
59
|
coreStepId: 'db_provision',
|
|
54
|
-
coreStepName: 'Netlify
|
|
55
|
-
coreStepDescription: () => 'Netlify
|
|
60
|
+
coreStepName: 'Netlify Database setup',
|
|
61
|
+
coreStepDescription: () => 'Netlify Database setup',
|
|
56
62
|
condition,
|
|
57
63
|
};
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import { copyFile, mkdir } from 'node:fs/promises';
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
3
|
import { pathExists } from 'path-exists';
|
|
4
|
-
import { readMigrationEntries } from './utils.js';
|
|
4
|
+
import { readMigrationEntries, getMigrationsSrc } from './utils.js';
|
|
5
5
|
import { validateMigrations, formatValidationErrors } from './validation.js';
|
|
6
6
|
const condition = async ({ featureFlags, constants, buildDir }) => {
|
|
7
7
|
if (!featureFlags?.netlify_build_db_setup) {
|
|
8
8
|
return false;
|
|
9
9
|
}
|
|
10
|
-
const srcDir = constants.DB_MIGRATIONS_SRC;
|
|
10
|
+
const srcDir = await getMigrationsSrc(buildDir, constants.DB_MIGRATIONS_SRC);
|
|
11
11
|
if (!srcDir) {
|
|
12
12
|
return false;
|
|
13
13
|
}
|
|
14
14
|
return pathExists(resolve(buildDir, srcDir));
|
|
15
15
|
};
|
|
16
16
|
const coreStep = async ({ constants, buildDir, systemLog }) => {
|
|
17
|
-
const
|
|
17
|
+
const migrationsSrc = await getMigrationsSrc(buildDir, constants.DB_MIGRATIONS_SRC);
|
|
18
|
+
const srcDir = resolve(buildDir, migrationsSrc);
|
|
18
19
|
const destDir = resolve(buildDir, constants.DB_MIGRATIONS_DIST);
|
|
19
|
-
const { dirNames, fileNames } = await readMigrationEntries(buildDir,
|
|
20
|
+
const { dirNames, fileNames } = await readMigrationEntries(buildDir, migrationsSrc);
|
|
20
21
|
if (dirNames.length === 0 && fileNames.length === 0) {
|
|
21
22
|
systemLog('No migration directories found, skipping copy.');
|
|
22
23
|
return {};
|
|
@@ -52,7 +53,7 @@ export const copyDbMigrations = {
|
|
|
52
53
|
event: 'onBuild',
|
|
53
54
|
coreStep,
|
|
54
55
|
coreStepId: 'db_migrations_copy',
|
|
55
|
-
coreStepName: 'Netlify
|
|
56
|
+
coreStepName: 'Netlify Database migrations',
|
|
56
57
|
coreStepDescription: () => 'Copy database migrations to internal directory',
|
|
57
58
|
condition,
|
|
58
59
|
quiet: true,
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the effective migrations source directory for the current build.
|
|
3
|
+
*
|
|
4
|
+
* If the user has set `database.migrations.path` in their config, or the new
|
|
5
|
+
* default `netlify/database/migrations` directory exists, that value will have
|
|
6
|
+
* been populated into `constants.DB_MIGRATIONS_SRC` upstream. If not, we fall
|
|
7
|
+
* back to the legacy `netlify/db/migrations` directory for backwards
|
|
8
|
+
* compatibility.
|
|
9
|
+
*/
|
|
10
|
+
export declare const getMigrationsSrc: (buildDir: string, configuredSrc: string | undefined) => Promise<string | undefined>;
|
|
1
11
|
export interface MigrationEntries {
|
|
2
12
|
dirNames: string[];
|
|
3
13
|
fileNames: string[];
|
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
import { readdir, stat } from 'node:fs/promises';
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
3
|
import { pathExists } from 'path-exists';
|
|
4
|
+
// TODO: Remove once we drop support for the legacy `netlify/db/migrations` directory.
|
|
5
|
+
const LEGACY_DB_MIGRATIONS_SRC = 'netlify/db/migrations';
|
|
6
|
+
/**
|
|
7
|
+
* Returns the effective migrations source directory for the current build.
|
|
8
|
+
*
|
|
9
|
+
* If the user has set `database.migrations.path` in their config, or the new
|
|
10
|
+
* default `netlify/database/migrations` directory exists, that value will have
|
|
11
|
+
* been populated into `constants.DB_MIGRATIONS_SRC` upstream. If not, we fall
|
|
12
|
+
* back to the legacy `netlify/db/migrations` directory for backwards
|
|
13
|
+
* compatibility.
|
|
14
|
+
*/
|
|
15
|
+
// TODO: Remove the legacy fallback once we drop support for `netlify/db/migrations`.
|
|
16
|
+
export const getMigrationsSrc = async (buildDir, configuredSrc) => {
|
|
17
|
+
if (configuredSrc) {
|
|
18
|
+
return configuredSrc;
|
|
19
|
+
}
|
|
20
|
+
if (await pathExists(resolve(buildDir, LEGACY_DB_MIGRATIONS_SRC))) {
|
|
21
|
+
return LEGACY_DB_MIGRATIONS_SRC;
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
};
|
|
4
25
|
export const readMigrationEntries = async (buildDir, migrationsSrc) => {
|
|
5
26
|
const empty = { dirNames: [], fileNames: [] };
|
|
6
27
|
if (!migrationsSrc) {
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
export declare const MIGRATION_DIR_PATTERN: RegExp;
|
|
2
2
|
export declare const MIGRATION_FILE_PATTERN: RegExp;
|
|
3
|
-
interface
|
|
3
|
+
interface MissingSqlFileError {
|
|
4
4
|
type: 'missing_sql_file';
|
|
5
5
|
dirName: string;
|
|
6
6
|
}
|
|
7
|
+
interface DuplicateMigrationNumberError {
|
|
8
|
+
type: 'duplicate_migration_number';
|
|
9
|
+
migrationNumber: string;
|
|
10
|
+
names: string[];
|
|
11
|
+
}
|
|
12
|
+
type ValidationError = MissingSqlFileError | DuplicateMigrationNumberError;
|
|
7
13
|
interface ValidationResult {
|
|
8
14
|
valid: true;
|
|
9
15
|
dirs: string[];
|
|
@@ -13,6 +19,7 @@ interface ValidationFailure {
|
|
|
13
19
|
valid: false;
|
|
14
20
|
errors: ValidationError[];
|
|
15
21
|
}
|
|
22
|
+
export declare const trackMigrationNumber: (numberToNames: Map<string, string[]>, name: string) => void;
|
|
16
23
|
export declare const validateMigrations: (dirNames: string[], fileNames: string[], existingSqlFiles: Set<string>) => ValidationResult | ValidationFailure;
|
|
17
24
|
export declare const formatValidationErrors: (errors: ValidationError[]) => string;
|
|
18
25
|
export {};
|
|
@@ -1,35 +1,56 @@
|
|
|
1
1
|
export const MIGRATION_DIR_PATTERN = /^\d+_[a-z0-9_-]+$/;
|
|
2
2
|
export const MIGRATION_FILE_PATTERN = /^\d+_[a-z0-9_-]+\.sql$/;
|
|
3
|
+
export const trackMigrationNumber = (numberToNames, name) => {
|
|
4
|
+
const key = /^(\d+)_/.exec(name)[1].replace(/^0+/, '') || '0';
|
|
5
|
+
const existing = numberToNames.get(key);
|
|
6
|
+
if (existing) {
|
|
7
|
+
existing.push(name);
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
numberToNames.set(key, [name]);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
3
13
|
export const validateMigrations = (dirNames, fileNames, existingSqlFiles) => {
|
|
4
14
|
const errors = [];
|
|
5
15
|
const matchingDirs = [];
|
|
16
|
+
const numberToNames = new Map();
|
|
6
17
|
for (const dirName of dirNames) {
|
|
7
18
|
if (!MIGRATION_DIR_PATTERN.test(dirName)) {
|
|
8
19
|
continue;
|
|
9
20
|
}
|
|
10
21
|
matchingDirs.push(dirName);
|
|
22
|
+
trackMigrationNumber(numberToNames, dirName);
|
|
11
23
|
if (!existingSqlFiles.has(dirName)) {
|
|
12
24
|
errors.push({ type: 'missing_sql_file', dirName });
|
|
13
25
|
}
|
|
14
26
|
}
|
|
15
|
-
if (errors.length > 0) {
|
|
16
|
-
return { valid: false, errors };
|
|
17
|
-
}
|
|
18
27
|
const matchingFiles = [];
|
|
19
28
|
for (const fileName of fileNames) {
|
|
20
29
|
if (MIGRATION_FILE_PATTERN.test(fileName)) {
|
|
21
30
|
matchingFiles.push(fileName);
|
|
31
|
+
trackMigrationNumber(numberToNames, fileName);
|
|
22
32
|
}
|
|
23
33
|
}
|
|
34
|
+
for (const [migrationNumber, names] of numberToNames) {
|
|
35
|
+
if (names.length > 1) {
|
|
36
|
+
errors.push({ type: 'duplicate_migration_number', migrationNumber, names: names.sort() });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (errors.length > 0) {
|
|
40
|
+
return { valid: false, errors };
|
|
41
|
+
}
|
|
24
42
|
return {
|
|
25
43
|
valid: true,
|
|
26
|
-
dirs:
|
|
27
|
-
files:
|
|
44
|
+
dirs: matchingDirs.sort(),
|
|
45
|
+
files: matchingFiles.sort(),
|
|
28
46
|
};
|
|
29
47
|
};
|
|
30
48
|
export const formatValidationErrors = (errors) => {
|
|
31
49
|
const lines = errors.map((error) => {
|
|
32
|
-
|
|
50
|
+
if (error.type === 'missing_sql_file') {
|
|
51
|
+
return ` - "${error.dirName}/migration.sql" is missing.`;
|
|
52
|
+
}
|
|
53
|
+
return ` - Duplicate migration number ${error.migrationNumber}: ${error.names.map((n) => `"${n}"`).join(', ')}`;
|
|
33
54
|
});
|
|
34
55
|
return `Database migration validation failed:\n${lines.join('\n')}`;
|
|
35
56
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from 'vitest';
|
|
2
|
-
import { MIGRATION_DIR_PATTERN, MIGRATION_FILE_PATTERN, validateMigrations, formatValidationErrors, } from './validation.js';
|
|
2
|
+
import { MIGRATION_DIR_PATTERN, MIGRATION_FILE_PATTERN, trackMigrationNumber, validateMigrations, formatValidationErrors, } from './validation.js';
|
|
3
3
|
describe('MIGRATION_DIR_PATTERN', () => {
|
|
4
4
|
const validNames = [
|
|
5
5
|
'1700000000_create-users',
|
|
@@ -55,6 +55,29 @@ describe('MIGRATION_FILE_PATTERN', () => {
|
|
|
55
55
|
expect(MIGRATION_FILE_PATTERN.test(name)).toBe(false);
|
|
56
56
|
});
|
|
57
57
|
});
|
|
58
|
+
describe('trackMigrationNumber', () => {
|
|
59
|
+
const cases = [
|
|
60
|
+
{ name: '001_create-users', expected: '1' },
|
|
61
|
+
{ name: '1_init', expected: '1' },
|
|
62
|
+
{ name: '0001_add-posts', expected: '1' },
|
|
63
|
+
{ name: '42_z', expected: '42' },
|
|
64
|
+
{ name: '1700000000_create-users', expected: '1700000000' },
|
|
65
|
+
{ name: '1700000000_create-users.sql', expected: '1700000000' },
|
|
66
|
+
{ name: '001_create-users.sql', expected: '1' },
|
|
67
|
+
{ name: '0000000000_init', expected: '0' },
|
|
68
|
+
];
|
|
69
|
+
test.each(cases)('tracks $name under key $expected', ({ name, expected }) => {
|
|
70
|
+
const map = new Map();
|
|
71
|
+
trackMigrationNumber(map, name);
|
|
72
|
+
expect(map.get(expected)).toEqual([name]);
|
|
73
|
+
});
|
|
74
|
+
test('groups names with the same migration number', () => {
|
|
75
|
+
const map = new Map();
|
|
76
|
+
trackMigrationNumber(map, '001_create-users');
|
|
77
|
+
trackMigrationNumber(map, '1_init');
|
|
78
|
+
expect(map.get('1')).toEqual(['001_create-users', '1_init']);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
58
81
|
describe('validateMigrations', () => {
|
|
59
82
|
test('returns valid result with sorted dirs when all dirs are valid', () => {
|
|
60
83
|
const dirNames = ['1700000001_add-posts', '1700000000_create-users'];
|
|
@@ -132,6 +155,52 @@ describe('validateMigrations', () => {
|
|
|
132
155
|
files: [],
|
|
133
156
|
});
|
|
134
157
|
});
|
|
158
|
+
test('returns error for duplicate migration numbers in dirs', () => {
|
|
159
|
+
const dirNames = ['001_create-users', '001_add-posts'];
|
|
160
|
+
const existingSqlFiles = new Set(['001_create-users', '001_add-posts']);
|
|
161
|
+
const result = validateMigrations(dirNames, [], existingSqlFiles);
|
|
162
|
+
expect(result).toEqual({
|
|
163
|
+
valid: false,
|
|
164
|
+
errors: [
|
|
165
|
+
{ type: 'duplicate_migration_number', migrationNumber: '1', names: ['001_add-posts', '001_create-users'] },
|
|
166
|
+
],
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
test('returns error for duplicate migration numbers in files', () => {
|
|
170
|
+
const fileNames = ['001_create-users.sql', '001_add-posts.sql'];
|
|
171
|
+
const result = validateMigrations([], fileNames, new Set());
|
|
172
|
+
expect(result).toEqual({
|
|
173
|
+
valid: false,
|
|
174
|
+
errors: [
|
|
175
|
+
{
|
|
176
|
+
type: 'duplicate_migration_number',
|
|
177
|
+
migrationNumber: '1',
|
|
178
|
+
names: ['001_add-posts.sql', '001_create-users.sql'],
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
test('returns error for duplicate migration numbers across dirs and files', () => {
|
|
184
|
+
const dirNames = ['001_create-users'];
|
|
185
|
+
const fileNames = ['001_add-posts.sql'];
|
|
186
|
+
const existingSqlFiles = new Set(['001_create-users']);
|
|
187
|
+
const result = validateMigrations(dirNames, fileNames, existingSqlFiles);
|
|
188
|
+
expect(result).toEqual({
|
|
189
|
+
valid: false,
|
|
190
|
+
errors: [
|
|
191
|
+
{ type: 'duplicate_migration_number', migrationNumber: '1', names: ['001_add-posts.sql', '001_create-users'] },
|
|
192
|
+
],
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
test('treats different zero-padded prefixes as duplicates', () => {
|
|
196
|
+
const dirNames = ['1_init', '001_setup'];
|
|
197
|
+
const existingSqlFiles = new Set(['1_init', '001_setup']);
|
|
198
|
+
const result = validateMigrations(dirNames, [], existingSqlFiles);
|
|
199
|
+
expect(result).toEqual({
|
|
200
|
+
valid: false,
|
|
201
|
+
errors: [{ type: 'duplicate_migration_number', migrationNumber: '1', names: ['001_setup', '1_init'] }],
|
|
202
|
+
});
|
|
203
|
+
});
|
|
135
204
|
});
|
|
136
205
|
describe('formatValidationErrors', () => {
|
|
137
206
|
test('formats missing_sql_file errors', () => {
|
|
@@ -147,4 +216,11 @@ describe('formatValidationErrors', () => {
|
|
|
147
216
|
expect(message).toContain('"1700000000_create-users/migration.sql" is missing');
|
|
148
217
|
expect(message).toContain('"1700000001_add-posts/migration.sql" is missing');
|
|
149
218
|
});
|
|
219
|
+
test('formats duplicate_migration_number errors', () => {
|
|
220
|
+
const message = formatValidationErrors([
|
|
221
|
+
{ type: 'duplicate_migration_number', migrationNumber: '1', names: ['001_add-posts', '001_create-users'] },
|
|
222
|
+
]);
|
|
223
|
+
expect(message).toContain('Database migration validation failed');
|
|
224
|
+
expect(message).toContain('Duplicate migration number 1: "001_add-posts", "001_create-users"');
|
|
225
|
+
});
|
|
150
226
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createReadStream, promises as fs, existsSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { fdir } from 'fdir';
|
|
4
|
+
import ignore from 'ignore';
|
|
4
5
|
import { minimatch } from 'minimatch';
|
|
5
6
|
import { LIKELY_SECRET_PREFIXES, SAFE_LISTED_VALUES } from './secret_prefixes.js';
|
|
6
7
|
/**
|
|
@@ -170,21 +171,16 @@ export function findLikelySecrets({ text, omitValuesFromEnhancedScan = [], }) {
|
|
|
170
171
|
*/
|
|
171
172
|
export async function getFilePathsToScan({ env, base }) {
|
|
172
173
|
const omitPathsAlways = ['.git/', '.cache/'];
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
// node_modules anywhere we will omit looking in those folders - this will allow repos
|
|
177
|
-
// that do commit node_modules to still scan them.
|
|
178
|
-
let ignoreNodeModules = false;
|
|
174
|
+
// Files/folders ignored by the repo's .gitignore should not be scanned, since they
|
|
175
|
+
// are not committed to the repo and are not part of the build output the user controls.
|
|
176
|
+
// This also naturally excludes things like node_modules when the repo gitignores it.
|
|
179
177
|
const gitignorePath = path.resolve(base, '.gitignore');
|
|
180
178
|
const gitignoreContents = existsSync(gitignorePath) ? await fs.readFile(gitignorePath, 'utf-8') : '';
|
|
181
|
-
|
|
182
|
-
ignoreNodeModules = true;
|
|
183
|
-
}
|
|
179
|
+
const gitIgnoreFilter = gitignoreContents ? ignore().add(gitignoreContents) : null;
|
|
184
180
|
let files = await new fdir()
|
|
185
181
|
.withRelativePaths()
|
|
186
|
-
.filter((
|
|
187
|
-
if (
|
|
182
|
+
.filter((filePath) => {
|
|
183
|
+
if (gitIgnoreFilter && filePath && gitIgnoreFilter.ignores(filePath)) {
|
|
188
184
|
return false;
|
|
189
185
|
}
|
|
190
186
|
return true;
|
|
@@ -25,11 +25,11 @@ interface NetlifyPlugin {
|
|
|
25
25
|
package: string;
|
|
26
26
|
inputs: PluginInputs;
|
|
27
27
|
}
|
|
28
|
-
interface
|
|
28
|
+
interface DatabaseMigrationsConfig {
|
|
29
29
|
path?: string;
|
|
30
30
|
}
|
|
31
|
-
interface
|
|
32
|
-
migrations?:
|
|
31
|
+
interface DatabaseConfig {
|
|
32
|
+
migrations?: DatabaseMigrationsConfig;
|
|
33
33
|
}
|
|
34
34
|
interface ImagesConfig {
|
|
35
35
|
remote_images: string[];
|
|
@@ -60,6 +60,6 @@ export interface NetlifyConfig {
|
|
|
60
60
|
/**
|
|
61
61
|
* object with options for database configuration
|
|
62
62
|
*/
|
|
63
|
-
|
|
63
|
+
database?: DatabaseConfig;
|
|
64
64
|
}
|
|
65
65
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/build",
|
|
3
|
-
"version": "35.
|
|
3
|
+
"version": "35.13.0",
|
|
4
4
|
"description": "Netlify build module",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": "./lib/index.js",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"@bugsnag/js": "^8.0.0",
|
|
70
70
|
"@netlify/blobs": "^10.4.4",
|
|
71
71
|
"@netlify/cache-utils": "^6.0.5",
|
|
72
|
-
"@netlify/config": "^24.
|
|
72
|
+
"@netlify/config": "^24.5.0",
|
|
73
73
|
"@netlify/edge-bundler": "14.9.19",
|
|
74
74
|
"@netlify/functions-utils": "^6.2.29",
|
|
75
75
|
"@netlify/git-utils": "^6.0.4",
|
|
@@ -86,6 +86,7 @@
|
|
|
86
86
|
"figures": "^6.0.0",
|
|
87
87
|
"filter-obj": "^6.0.0",
|
|
88
88
|
"hot-shots": "11.4.0",
|
|
89
|
+
"ignore": "^7.0.0",
|
|
89
90
|
"indent-string": "^5.0.0",
|
|
90
91
|
"is-plain-obj": "^4.0.0",
|
|
91
92
|
"keep-func-props": "^6.0.0",
|
|
@@ -152,5 +153,5 @@
|
|
|
152
153
|
"engines": {
|
|
153
154
|
"node": ">=18.14.0"
|
|
154
155
|
},
|
|
155
|
-
"gitHead": "
|
|
156
|
+
"gitHead": "c77982ba1d1886e81f1a517318fcf93123f2a260"
|
|
156
157
|
}
|