@littlebearapps/platform-admin-sdk 2.2.0 → 2.3.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/dist/check-upgrade.d.ts +29 -0
- package/dist/check-upgrade.js +97 -0
- package/dist/index.js +59 -4
- package/dist/manifest.d.ts +2 -0
- package/dist/scaffold.js +5 -1
- package/dist/templates.d.ts +6 -1
- package/dist/templates.js +21 -1
- package/dist/upgrade.d.ts +1 -0
- package/dist/upgrade.js +21 -2
- package/package.json +1 -1
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-flight check for upgrades — detects potential issues before writing files.
|
|
3
|
+
*
|
|
4
|
+
* Checks for:
|
|
5
|
+
* - Template naming collisions
|
|
6
|
+
* - Placeholder values left in wrangler configs (e.g., "YOUR_DB_ID")
|
|
7
|
+
* - Migration number conflicts
|
|
8
|
+
* - Excluded files from the manifest
|
|
9
|
+
*/
|
|
10
|
+
import type { Tier } from './prompts.js';
|
|
11
|
+
export interface CheckResult {
|
|
12
|
+
currentVersion: string;
|
|
13
|
+
targetVersion: string;
|
|
14
|
+
currentTier: string;
|
|
15
|
+
targetTier: string;
|
|
16
|
+
placeholders: string[];
|
|
17
|
+
collisions: string[];
|
|
18
|
+
migrationConflicts: string[];
|
|
19
|
+
excludedFiles: string[];
|
|
20
|
+
ok: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Run pre-flight checks for an upgrade without modifying any files.
|
|
24
|
+
*
|
|
25
|
+
* @param projectDir - Path to the project directory
|
|
26
|
+
* @param targetTier - Optional tier to upgrade to (defaults to current tier)
|
|
27
|
+
* @returns CheckResult with all detected issues
|
|
28
|
+
*/
|
|
29
|
+
export declare function checkUpgrade(projectDir: string, targetTier?: Tier): CheckResult;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-flight check for upgrades — detects potential issues before writing files.
|
|
3
|
+
*
|
|
4
|
+
* Checks for:
|
|
5
|
+
* - Template naming collisions
|
|
6
|
+
* - Placeholder values left in wrangler configs (e.g., "YOUR_DB_ID")
|
|
7
|
+
* - Migration number conflicts
|
|
8
|
+
* - Excluded files from the manifest
|
|
9
|
+
*/
|
|
10
|
+
import { readFileSync, existsSync, readdirSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { detectCollisions, SDK_VERSION, getFilesForTier, isMigrationFile } from './templates.js';
|
|
13
|
+
import { readManifest, MANIFEST_FILENAME } from './manifest.js';
|
|
14
|
+
import { findHighestMigration, getMigrationNumber } from './migrations.js';
|
|
15
|
+
/** Patterns that indicate placeholder values left by scaffolding. */
|
|
16
|
+
const PLACEHOLDER_PATTERNS = [
|
|
17
|
+
/YOUR_DB_ID/,
|
|
18
|
+
/YOUR_KV_ID/,
|
|
19
|
+
/YOUR_QUEUE_ID/,
|
|
20
|
+
/YOUR_R2_BUCKET/,
|
|
21
|
+
/PLACEHOLDER/,
|
|
22
|
+
/your-database-id/,
|
|
23
|
+
/your-kv-namespace-id/,
|
|
24
|
+
/your-queue-id/,
|
|
25
|
+
];
|
|
26
|
+
/**
|
|
27
|
+
* Run pre-flight checks for an upgrade without modifying any files.
|
|
28
|
+
*
|
|
29
|
+
* @param projectDir - Path to the project directory
|
|
30
|
+
* @param targetTier - Optional tier to upgrade to (defaults to current tier)
|
|
31
|
+
* @returns CheckResult with all detected issues
|
|
32
|
+
*/
|
|
33
|
+
export function checkUpgrade(projectDir, targetTier) {
|
|
34
|
+
const manifest = readManifest(projectDir);
|
|
35
|
+
if (!manifest) {
|
|
36
|
+
throw new Error(`No ${MANIFEST_FILENAME} found in ${projectDir}.\n` +
|
|
37
|
+
`Run \`platform-admin-sdk adopt\` first.`);
|
|
38
|
+
}
|
|
39
|
+
const effectiveTier = targetTier ?? manifest.tier;
|
|
40
|
+
const result = {
|
|
41
|
+
currentVersion: manifest.sdkVersion,
|
|
42
|
+
targetVersion: SDK_VERSION,
|
|
43
|
+
currentTier: manifest.tier,
|
|
44
|
+
targetTier: effectiveTier,
|
|
45
|
+
placeholders: [],
|
|
46
|
+
collisions: [],
|
|
47
|
+
migrationConflicts: [],
|
|
48
|
+
excludedFiles: [...(manifest.excludeFromUpgrade ?? [])],
|
|
49
|
+
ok: true,
|
|
50
|
+
};
|
|
51
|
+
// 1. Check for naming collisions
|
|
52
|
+
result.collisions = detectCollisions(effectiveTier);
|
|
53
|
+
// 2. Scan wrangler.*.jsonc files for placeholder values
|
|
54
|
+
if (existsSync(projectDir)) {
|
|
55
|
+
let dirEntries;
|
|
56
|
+
try {
|
|
57
|
+
dirEntries = readdirSync(projectDir);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
dirEntries = [];
|
|
61
|
+
}
|
|
62
|
+
const wranglerFiles = dirEntries.filter((f) => f.startsWith('wrangler') && (f.endsWith('.jsonc') || f.endsWith('.json')));
|
|
63
|
+
for (const wf of wranglerFiles) {
|
|
64
|
+
const filePath = join(projectDir, wf);
|
|
65
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
66
|
+
for (const pattern of PLACEHOLDER_PATTERNS) {
|
|
67
|
+
if (pattern.test(content)) {
|
|
68
|
+
result.placeholders.push(`${wf}: contains ${pattern.source}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// 3. Check for migration conflicts
|
|
74
|
+
const migrationsDir = join(projectDir, 'storage/d1/migrations');
|
|
75
|
+
const userHighest = findHighestMigration(migrationsDir);
|
|
76
|
+
const files = getFilesForTier(effectiveTier);
|
|
77
|
+
const migrationFiles = files.filter((f) => isMigrationFile(f));
|
|
78
|
+
// Find new scaffold migrations that would need to be renumbered
|
|
79
|
+
const newScaffoldMigrations = migrationFiles.filter((f) => {
|
|
80
|
+
const num = getMigrationNumber(f.dest);
|
|
81
|
+
return num !== null && num > manifest.highestScaffoldMigration;
|
|
82
|
+
});
|
|
83
|
+
// Check if any new scaffold migration numbers overlap with existing user migrations
|
|
84
|
+
for (const mf of newScaffoldMigrations) {
|
|
85
|
+
const num = getMigrationNumber(mf.dest);
|
|
86
|
+
if (num !== null && num <= userHighest && num > manifest.highestScaffoldMigration) {
|
|
87
|
+
const filename = mf.dest.split('/').pop() ?? mf.dest;
|
|
88
|
+
result.migrationConflicts.push(`${filename} (number ${num}) overlaps with existing user migrations (highest: ${userHighest}) — will be renumbered`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// 4. Determine overall ok status
|
|
92
|
+
result.ok =
|
|
93
|
+
result.collisions.length === 0 &&
|
|
94
|
+
result.placeholders.length === 0 &&
|
|
95
|
+
result.migrationConflicts.length === 0;
|
|
96
|
+
return result;
|
|
97
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,7 @@ import { collectOptions, isValidTier } from './prompts.js';
|
|
|
24
24
|
import { scaffold } from './scaffold.js';
|
|
25
25
|
import { upgrade } from './upgrade.js';
|
|
26
26
|
import { adopt } from './adopt.js';
|
|
27
|
+
import { checkUpgrade } from './check-upgrade.js';
|
|
27
28
|
import { SDK_VERSION } from './templates.js';
|
|
28
29
|
const BANNER = `
|
|
29
30
|
${pc.bold(pc.cyan('Platform Admin SDK'))} — Cloudflare Cost Protection
|
|
@@ -109,11 +110,15 @@ const upgradeCmd = new Command('upgrade')
|
|
|
109
110
|
});
|
|
110
111
|
console.log();
|
|
111
112
|
const total = result.created.length + result.updated.length + result.migrations.length;
|
|
112
|
-
if (total === 0 && result.skipped.length === 0) {
|
|
113
|
+
if (total === 0 && result.skipped.length === 0 && result.excluded.length === 0) {
|
|
113
114
|
console.log(pc.green(' Already up to date.'));
|
|
114
115
|
}
|
|
115
116
|
else {
|
|
116
|
-
|
|
117
|
+
let summary = ` ${pc.green(`${result.created.length} created`)}, ${pc.cyan(`${result.updated.length} updated`)}, ${pc.yellow(`${result.skipped.length} skipped`)}, ${pc.green(`${result.migrations.length} migrations`)}`;
|
|
118
|
+
if (result.excluded.length > 0) {
|
|
119
|
+
summary += `, ${pc.dim(`${result.excluded.length} excluded`)}`;
|
|
120
|
+
}
|
|
121
|
+
console.log(summary);
|
|
117
122
|
}
|
|
118
123
|
if (result.removed.length > 0) {
|
|
119
124
|
console.log(` ${pc.yellow(`${result.removed.length} files removed from SDK (kept on disk)`)}`);
|
|
@@ -170,6 +175,55 @@ const adoptCmd = new Command('adopt')
|
|
|
170
175
|
console.log(` ${pc.dim('You can now run:')} ${pc.cyan('platform-admin-sdk upgrade')}`);
|
|
171
176
|
console.log();
|
|
172
177
|
});
|
|
178
|
+
// --- Check-upgrade command ---
|
|
179
|
+
const checkUpgradeCmd = new Command('check-upgrade')
|
|
180
|
+
.description('Pre-flight check for upgrades — detect issues without writing files')
|
|
181
|
+
.argument('[project-dir]', 'Path to the project directory', '.')
|
|
182
|
+
.option('--tier <tier>', 'Target tier to check against')
|
|
183
|
+
.action(async (projectDirArg, cmdOpts) => {
|
|
184
|
+
if (cmdOpts.tier && !isValidTier(cmdOpts.tier)) {
|
|
185
|
+
console.error(pc.red(` Error: Invalid tier "${cmdOpts.tier}". Must be one of: minimal, standard, full`));
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
const projectDir = resolve(process.cwd(), projectDirArg);
|
|
189
|
+
console.log(` ${pc.bold('Checking')}: ${projectDir}`);
|
|
190
|
+
console.log();
|
|
191
|
+
const result = checkUpgrade(projectDir, cmdOpts.tier);
|
|
192
|
+
console.log(` ${pc.bold('Current')}: v${result.currentVersion} (${result.currentTier})`);
|
|
193
|
+
console.log(` ${pc.bold('Target')}: v${result.targetVersion} (${result.targetTier})`);
|
|
194
|
+
console.log();
|
|
195
|
+
if (result.collisions.length > 0) {
|
|
196
|
+
console.log(pc.red(` Collisions (${result.collisions.length}):`));
|
|
197
|
+
for (const c of result.collisions)
|
|
198
|
+
console.log(` - ${c}`);
|
|
199
|
+
console.log();
|
|
200
|
+
}
|
|
201
|
+
if (result.placeholders.length > 0) {
|
|
202
|
+
console.log(pc.yellow(` Placeholders (${result.placeholders.length}):`));
|
|
203
|
+
for (const p of result.placeholders)
|
|
204
|
+
console.log(` - ${p}`);
|
|
205
|
+
console.log();
|
|
206
|
+
}
|
|
207
|
+
if (result.migrationConflicts.length > 0) {
|
|
208
|
+
console.log(pc.yellow(` Migration conflicts (${result.migrationConflicts.length}):`));
|
|
209
|
+
for (const m of result.migrationConflicts)
|
|
210
|
+
console.log(` - ${m}`);
|
|
211
|
+
console.log();
|
|
212
|
+
}
|
|
213
|
+
if (result.excludedFiles.length > 0) {
|
|
214
|
+
console.log(pc.dim(` Excluded files (${result.excludedFiles.length}):`));
|
|
215
|
+
for (const e of result.excludedFiles)
|
|
216
|
+
console.log(` - ${e}`);
|
|
217
|
+
console.log();
|
|
218
|
+
}
|
|
219
|
+
if (result.ok) {
|
|
220
|
+
console.log(pc.green(' All checks passed. Safe to upgrade.'));
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
console.log(pc.yellow(' Issues found. Review before upgrading.'));
|
|
224
|
+
}
|
|
225
|
+
console.log();
|
|
226
|
+
});
|
|
173
227
|
// --- Main program ---
|
|
174
228
|
const program = new Command()
|
|
175
229
|
.name('platform-admin-sdk')
|
|
@@ -177,12 +231,13 @@ const program = new Command()
|
|
|
177
231
|
.version(SDK_VERSION)
|
|
178
232
|
.addCommand(scaffoldCmd)
|
|
179
233
|
.addCommand(upgradeCmd)
|
|
180
|
-
.addCommand(adoptCmd)
|
|
234
|
+
.addCommand(adoptCmd)
|
|
235
|
+
.addCommand(checkUpgradeCmd);
|
|
181
236
|
async function main() {
|
|
182
237
|
console.log(BANNER);
|
|
183
238
|
// Backward compat: if first arg isn't a known subcommand, treat it as `scaffold <arg>`
|
|
184
239
|
const args = process.argv.slice(2);
|
|
185
|
-
const subcommands = ['scaffold', 'upgrade', 'adopt', 'help', '--help', '-h', '--version', '-V'];
|
|
240
|
+
const subcommands = ['scaffold', 'upgrade', 'adopt', 'check-upgrade', 'help', '--help', '-h', '--version', '-V'];
|
|
186
241
|
if (args.length > 0 && !subcommands.includes(args[0])) {
|
|
187
242
|
// Inject 'scaffold' as the subcommand
|
|
188
243
|
process.argv.splice(2, 0, 'scaffold');
|
package/dist/manifest.d.ts
CHANGED
|
@@ -28,6 +28,8 @@ export interface ScaffoldManifest {
|
|
|
28
28
|
files: Record<string, string>;
|
|
29
29
|
/** Highest migration number owned by the scaffolder (user migrations are higher). */
|
|
30
30
|
highestScaffoldMigration: number;
|
|
31
|
+
/** Relative file paths to skip during upgrade (user-managed files). */
|
|
32
|
+
excludeFromUpgrade?: string[];
|
|
31
33
|
}
|
|
32
34
|
/** SHA-256 hash of file content. */
|
|
33
35
|
export declare function hashContent(content: string): string;
|
package/dist/scaffold.js
CHANGED
|
@@ -6,7 +6,7 @@ import { resolve, dirname, join } from 'node:path';
|
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import Handlebars from 'handlebars';
|
|
8
8
|
import pc from 'picocolors';
|
|
9
|
-
import { getFilesForTier, SDK_VERSION } from './templates.js';
|
|
9
|
+
import { getFilesForTier, SDK_VERSION, detectCollisions } from './templates.js';
|
|
10
10
|
import { hashContent, buildManifest, writeManifest, MANIFEST_FILENAME } from './manifest.js';
|
|
11
11
|
import { findHighestMigration } from './migrations.js';
|
|
12
12
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -35,6 +35,10 @@ export async function scaffold(options, outputDir) {
|
|
|
35
35
|
}
|
|
36
36
|
throw new Error(`Directory already exists: ${outputDir}`);
|
|
37
37
|
}
|
|
38
|
+
const collisions = detectCollisions(options.tier);
|
|
39
|
+
if (collisions.length > 0) {
|
|
40
|
+
throw new Error(`Template naming collisions detected:\n${collisions.map((c) => ` - ${c}`).join('\n')}`);
|
|
41
|
+
}
|
|
38
42
|
const templatesDir = getTemplatesDir();
|
|
39
43
|
const files = getFilesForTier(options.tier);
|
|
40
44
|
const context = {
|
package/dist/templates.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { Tier } from './prompts.js';
|
|
8
8
|
/** Single source of truth for the SDK version. */
|
|
9
|
-
export declare const SDK_VERSION = "2.
|
|
9
|
+
export declare const SDK_VERSION = "2.3.0";
|
|
10
10
|
/** Returns true if `to` is the same or higher tier than `from`. */
|
|
11
11
|
export declare function isTierUpgradeOrSame(from: Tier, to: Tier): boolean;
|
|
12
12
|
/** Check if a template file is a numbered migration (not seed.sql). */
|
|
@@ -19,4 +19,9 @@ export interface TemplateFile {
|
|
|
19
19
|
/** Whether this file uses Handlebars templating */
|
|
20
20
|
template: boolean;
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Detect naming collisions where two template files resolve to the same destination path.
|
|
24
|
+
* Returns an array of collision descriptions (empty = no collisions).
|
|
25
|
+
*/
|
|
26
|
+
export declare function detectCollisions(tier: Tier): string[];
|
|
22
27
|
export declare function getFilesForTier(tier: Tier): TemplateFile[];
|
package/dist/templates.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* All other files are copied verbatim.
|
|
6
6
|
*/
|
|
7
7
|
/** Single source of truth for the SDK version. */
|
|
8
|
-
export const SDK_VERSION = '2.
|
|
8
|
+
export const SDK_VERSION = '2.3.0';
|
|
9
9
|
/** Tier ordering for upgrade validation. */
|
|
10
10
|
const TIER_ORDER = { minimal: 0, standard: 1, full: 2 };
|
|
11
11
|
/** Returns true if `to` is the same or higher tier than `from`. */
|
|
@@ -566,6 +566,26 @@ const FULL_FILES = [
|
|
|
566
566
|
{ src: 'full/tests/integration/r2-archive.test.ts', dest: 'tests/integration/r2-archive.test.ts', template: false },
|
|
567
567
|
{ src: 'full/tests/integration/feedback-schema.test.ts', dest: 'tests/integration/feedback-schema.test.ts', template: false },
|
|
568
568
|
];
|
|
569
|
+
/**
|
|
570
|
+
* Detect naming collisions where two template files resolve to the same destination path.
|
|
571
|
+
* Returns an array of collision descriptions (empty = no collisions).
|
|
572
|
+
*/
|
|
573
|
+
export function detectCollisions(tier) {
|
|
574
|
+
const files = getFilesForTier(tier);
|
|
575
|
+
const seen = new Map();
|
|
576
|
+
const collisions = [];
|
|
577
|
+
for (const file of files) {
|
|
578
|
+
const dest = file.dest;
|
|
579
|
+
const existing = seen.get(dest);
|
|
580
|
+
if (existing) {
|
|
581
|
+
collisions.push(`"${existing}" and "${file.src}" both resolve to "${dest}"`);
|
|
582
|
+
}
|
|
583
|
+
else {
|
|
584
|
+
seen.set(dest, file.src);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return collisions;
|
|
588
|
+
}
|
|
569
589
|
export function getFilesForTier(tier) {
|
|
570
590
|
const files = [...SHARED_FILES];
|
|
571
591
|
if (tier === 'standard' || tier === 'full') {
|
package/dist/upgrade.d.ts
CHANGED
package/dist/upgrade.js
CHANGED
|
@@ -14,7 +14,7 @@ import { resolve, dirname, join } from 'node:path';
|
|
|
14
14
|
import { fileURLToPath } from 'node:url';
|
|
15
15
|
import Handlebars from 'handlebars';
|
|
16
16
|
import pc from 'picocolors';
|
|
17
|
-
import { getFilesForTier, SDK_VERSION, isMigrationFile, isTierUpgradeOrSame } from './templates.js';
|
|
17
|
+
import { getFilesForTier, SDK_VERSION, isMigrationFile, isTierUpgradeOrSame, detectCollisions } from './templates.js';
|
|
18
18
|
import { readManifest, writeManifest, buildManifest, hashContent, MANIFEST_FILENAME, } from './manifest.js';
|
|
19
19
|
import { findHighestMigration, getMigrationNumber, planMigrations } from './migrations.js';
|
|
20
20
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -46,7 +46,11 @@ export async function upgrade(projectDir, options = {}) {
|
|
|
46
46
|
}
|
|
47
47
|
if (manifest.sdkVersion === SDK_VERSION && manifest.tier === targetTier) {
|
|
48
48
|
console.log(pc.green(` Already up to date (SDK ${SDK_VERSION}, tier ${targetTier}).`));
|
|
49
|
-
return { created: [], updated: [], skipped: [], removed: [], migrations: [] };
|
|
49
|
+
return { created: [], updated: [], skipped: [], removed: [], migrations: [], excluded: [] };
|
|
50
|
+
}
|
|
51
|
+
const collisions = detectCollisions(targetTier);
|
|
52
|
+
if (collisions.length > 0) {
|
|
53
|
+
throw new Error(`Template naming collisions detected:\n${collisions.map((c) => ` - ${c}`).join('\n')}`);
|
|
50
54
|
}
|
|
51
55
|
const templatesDir = getTemplatesDir();
|
|
52
56
|
const files = getFilesForTier(targetTier);
|
|
@@ -59,12 +63,14 @@ export async function upgrade(projectDir, options = {}) {
|
|
|
59
63
|
defaultAssignee: manifest.context.defaultAssignee,
|
|
60
64
|
sdkVersion: SDK_VERSION,
|
|
61
65
|
};
|
|
66
|
+
const excludeSet = new Set(manifest.excludeFromUpgrade ?? []);
|
|
62
67
|
const result = {
|
|
63
68
|
created: [],
|
|
64
69
|
updated: [],
|
|
65
70
|
skipped: [],
|
|
66
71
|
removed: [],
|
|
67
72
|
migrations: [],
|
|
73
|
+
excluded: [],
|
|
68
74
|
};
|
|
69
75
|
// Separate regular files from migrations
|
|
70
76
|
const regularFiles = files.filter((f) => !isMigrationFile(f));
|
|
@@ -75,6 +81,16 @@ export async function upgrade(projectDir, options = {}) {
|
|
|
75
81
|
const srcPath = join(templatesDir, file.src);
|
|
76
82
|
const destRelative = renderString(file.dest, context);
|
|
77
83
|
const destPath = join(projectDir, destRelative);
|
|
84
|
+
if (excludeSet.has(destRelative)) {
|
|
85
|
+
console.log(` ${pc.dim('exclude')} ${destRelative} ${pc.dim('(in excludeFromUpgrade)')}`);
|
|
86
|
+
result.excluded.push(destRelative);
|
|
87
|
+
// Preserve existing hash in new manifest if file is on disk
|
|
88
|
+
if (existsSync(destPath)) {
|
|
89
|
+
const diskContent = readFileSync(destPath, 'utf-8');
|
|
90
|
+
newFileHashes[destRelative] = hashContent(diskContent);
|
|
91
|
+
}
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
78
94
|
if (!existsSync(srcPath))
|
|
79
95
|
continue;
|
|
80
96
|
const raw = readFileSync(srcPath, 'utf-8');
|
|
@@ -174,6 +190,9 @@ export async function upgrade(projectDir, options = {}) {
|
|
|
174
190
|
// --- Write updated manifest ---
|
|
175
191
|
if (!options.dryRun) {
|
|
176
192
|
const newManifest = buildManifest(SDK_VERSION, targetTier, manifest.context, newFileHashes, highestScaffoldMig);
|
|
193
|
+
if (manifest.excludeFromUpgrade && manifest.excludeFromUpgrade.length > 0) {
|
|
194
|
+
newManifest.excludeFromUpgrade = manifest.excludeFromUpgrade;
|
|
195
|
+
}
|
|
177
196
|
writeManifest(projectDir, newManifest);
|
|
178
197
|
}
|
|
179
198
|
return result;
|
package/package.json
CHANGED