@svadmin/create 0.12.5 → 0.13.1

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/README.md CHANGED
@@ -66,6 +66,28 @@ Ejected files are placed in `src/components/svadmin/`. Then pass them via the `c
66
66
 
67
67
  `Layout` · `Sidebar` · `Header` · `LoginPage` · `AutoTable` · `AutoForm` · `ShowPage` · `ProfilePage` · `StatsCard` · `AuditLogDrawer` · `LiveIndicator` · `CommandPalette` · `AICommandBar` · `ChatDialog` · `PasswordInput` · `BooleanField` · `FieldRenderer` · `MarkdownRenderer` · `AnomalyBadge` · `Toast` · `ConfirmDialog` · `TooltipButton` · `Breadcrumbs` · `ConfigErrorScreen` · `DevTools`
68
68
 
69
+ ## Project Maintenance
70
+
71
+ Check the current project against the dependency versions shipped with the CLI. This command is read-only and does not access the network:
72
+
73
+ ```bash
74
+ npx @svadmin/create doctor
75
+ ```
76
+
77
+ Preview an upgrade plan without changing `package.json`:
78
+
79
+ ```bash
80
+ npx @svadmin/create upgrade
81
+ ```
82
+
83
+ Apply the plan explicitly. The CLI creates a timestamped `package.json.svadmin-backup-*` file before replacing `package.json`:
84
+
85
+ ```bash
86
+ npx @svadmin/create upgrade --write
87
+ ```
88
+
89
+ Both commands accept an optional project-directory argument. Upgrade only manages dependencies known by the shipped scaffold; custom dependencies, scripts, and other package fields are preserved.
90
+
69
91
  ## Development
70
92
 
71
93
  ```bash
package/dist/index.js CHANGED
@@ -5027,8 +5027,495 @@ import fs from "node:fs";
5027
5027
  import path from "node:path";
5028
5028
  import { fileURLToPath } from "node:url";
5029
5029
  import { createRequire as createRequire2 } from "node:module";
5030
+ import { spawnSync } from "node:child_process";
5031
+
5032
+ // src/project-manifest.ts
5033
+ import { readFileSync } from "node:fs";
5034
+ var DATA_PROVIDER_CHOICES = ["simple-rest", "supabase", "graphql", "none"];
5035
+ var AUTH_PROVIDER_CHOICES = ["mock", "jwt", "supabase", "none"];
5036
+ function assertJsonObject(candidate, path) {
5037
+ if (candidate === null || typeof candidate !== "object" || Array.isArray(candidate)) {
5038
+ throw new Error(`${path} must be an object`);
5039
+ }
5040
+ }
5041
+ function assertNonEmptyString(candidate, path) {
5042
+ if (typeof candidate !== "string" || candidate.length === 0) {
5043
+ throw new Error(`${path} must be a non-empty string`);
5044
+ }
5045
+ }
5046
+ function assertStringRecord(candidate, path) {
5047
+ assertJsonObject(candidate, path);
5048
+ for (const [key, entry] of Object.entries(candidate)) {
5049
+ assertNonEmptyString(entry, `${path}.${key}`);
5050
+ }
5051
+ }
5052
+ function assertSelectionMap(candidate, choices, path) {
5053
+ assertJsonObject(candidate, path);
5054
+ for (const choice of choices) {
5055
+ const packs = candidate[choice];
5056
+ if (!Array.isArray(packs) || packs.some((pack) => typeof pack !== "string")) {
5057
+ throw new Error(`${path}.${choice} must be an array of dependency-pack names`);
5058
+ }
5059
+ }
5060
+ }
5061
+ function assertDependencyPacks(candidate) {
5062
+ assertJsonObject(candidate, "scaffold manifest.svadmin.dependencyPacks");
5063
+ for (const [packName, pack] of Object.entries(candidate)) {
5064
+ assertStringRecord(pack, `scaffold manifest.svadmin.dependencyPacks.${packName}`);
5065
+ }
5066
+ }
5067
+ function assertReferencedPacksExist(dependencyPacks, selectedPacks) {
5068
+ for (const packName of selectedPacks) {
5069
+ if (!(packName in dependencyPacks)) {
5070
+ throw new Error(`scaffold manifest references unknown dependency pack: ${packName}`);
5071
+ }
5072
+ }
5073
+ }
5074
+ function assertScaffoldManifest(candidate) {
5075
+ assertJsonObject(candidate, "scaffold manifest");
5076
+ assertNonEmptyString(candidate.name, "scaffold manifest.name");
5077
+ assertNonEmptyString(candidate.version, "scaffold manifest.version");
5078
+ if (candidate.private !== true) {
5079
+ throw new Error("scaffold manifest.private must be true");
5080
+ }
5081
+ assertNonEmptyString(candidate.type, "scaffold manifest.type");
5082
+ assertStringRecord(candidate.scripts, "scaffold manifest.scripts");
5083
+ assertStringRecord(candidate.dependencies, "scaffold manifest.dependencies");
5084
+ assertStringRecord(candidate.devDependencies, "scaffold manifest.devDependencies");
5085
+ assertJsonObject(candidate.svadmin, "scaffold manifest.svadmin");
5086
+ assertDependencyPacks(candidate.svadmin.dependencyPacks);
5087
+ assertSelectionMap(candidate.svadmin.dataProviders, DATA_PROVIDER_CHOICES, "scaffold manifest.svadmin.dataProviders");
5088
+ assertSelectionMap(candidate.svadmin.authProviders, AUTH_PROVIDER_CHOICES, "scaffold manifest.svadmin.authProviders");
5089
+ assertReferencedPacksExist(candidate.svadmin.dependencyPacks, [
5090
+ ...Object.values(candidate.svadmin.dataProviders).flat(),
5091
+ ...Object.values(candidate.svadmin.authProviders).flat()
5092
+ ]);
5093
+ }
5094
+ function loadScaffoldManifest(manifestPath) {
5095
+ const parsed = JSON.parse(readFileSync(manifestPath, "utf8"));
5096
+ assertScaffoldManifest(parsed);
5097
+ return parsed;
5098
+ }
5099
+ function mergeDependencyPack(dependencies, pack, packName) {
5100
+ for (const [packageName, version] of Object.entries(pack)) {
5101
+ const existingVersion = dependencies[packageName];
5102
+ if (existingVersion !== undefined && existingVersion !== version) {
5103
+ throw new Error(`Dependency ${packageName} has conflicting versions ${existingVersion} and ${version} in pack ${packName}`);
5104
+ }
5105
+ dependencies[packageName] = version;
5106
+ }
5107
+ }
5108
+ function createProjectPackageJson(scaffold, options) {
5109
+ const dependencies = { ...scaffold.dependencies };
5110
+ const selectedPacks = new Set([
5111
+ ...scaffold.svadmin.dataProviders[options.dataProvider],
5112
+ ...scaffold.svadmin.authProviders[options.authProvider]
5113
+ ]);
5114
+ for (const packName of selectedPacks) {
5115
+ mergeDependencyPack(dependencies, scaffold.svadmin.dependencyPacks[packName], packName);
5116
+ }
5117
+ return {
5118
+ name: options.projectName.trim(),
5119
+ version: scaffold.version,
5120
+ private: scaffold.private,
5121
+ type: scaffold.type,
5122
+ scripts: { ...scaffold.scripts },
5123
+ dependencies,
5124
+ devDependencies: { ...scaffold.devDependencies }
5125
+ };
5126
+ }
5127
+
5128
+ // src/project-maintenance.ts
5129
+ import {
5130
+ constants,
5131
+ copyFileSync,
5132
+ readFileSync as readFileSync2,
5133
+ renameSync,
5134
+ unlinkSync,
5135
+ writeFileSync
5136
+ } from "node:fs";
5137
+ function parseDependencyMap(candidate, path) {
5138
+ if (candidate === undefined)
5139
+ return;
5140
+ assertStringRecord(candidate, path);
5141
+ return { ...candidate };
5142
+ }
5143
+ function parseMaintainedPackageJson(packageJsonCandidate) {
5144
+ assertJsonObject(packageJsonCandidate, "package.json");
5145
+ const packageJson = structuredClone(packageJsonCandidate);
5146
+ const dependencies = parseDependencyMap(packageJsonCandidate.dependencies, "package.json.dependencies");
5147
+ const devDependencies = parseDependencyMap(packageJsonCandidate.devDependencies, "package.json.devDependencies");
5148
+ if (dependencies !== undefined)
5149
+ packageJson.dependencies = dependencies;
5150
+ if (devDependencies !== undefined)
5151
+ packageJson.devDependencies = devDependencies;
5152
+ return packageJson;
5153
+ }
5154
+ function readMaintainedPackageJson(packagePath) {
5155
+ const parsed = JSON.parse(readFileSync2(packagePath, "utf8"));
5156
+ return parseMaintainedPackageJson(parsed);
5157
+ }
5158
+ function allProjectDependencies(project) {
5159
+ return { ...project.dependencies ?? {}, ...project.devDependencies ?? {} };
5160
+ }
5161
+ function selectedDependencyPacks(project, scaffold) {
5162
+ const currentDependencies = allProjectDependencies(project);
5163
+ const directlySelectedPacks = new Set;
5164
+ for (const [packName, pack] of Object.entries(scaffold.svadmin.dependencyPacks)) {
5165
+ const providerPackage = Object.keys(pack).find((packageName) => packageName.startsWith("@svadmin/"));
5166
+ if (providerPackage !== undefined && currentDependencies[providerPackage] !== undefined) {
5167
+ directlySelectedPacks.add(packName);
5168
+ }
5169
+ }
5170
+ const selectedPacks = new Set(directlySelectedPacks);
5171
+ const selections = [
5172
+ ...Object.values(scaffold.svadmin.dataProviders),
5173
+ ...Object.values(scaffold.svadmin.authProviders)
5174
+ ];
5175
+ for (const packs of selections) {
5176
+ if (packs.some((packName) => directlySelectedPacks.has(packName))) {
5177
+ for (const packName of packs)
5178
+ selectedPacks.add(packName);
5179
+ }
5180
+ }
5181
+ return selectedPacks;
5182
+ }
5183
+ function desiredDependencies(project, scaffold) {
5184
+ const desired = new Map;
5185
+ for (const [packageName, version] of Object.entries(scaffold.dependencies)) {
5186
+ desired.set(packageName, { packageName, version, section: "dependencies" });
5187
+ }
5188
+ for (const [packageName, version] of Object.entries(scaffold.devDependencies)) {
5189
+ desired.set(packageName, { packageName, version, section: "devDependencies" });
5190
+ }
5191
+ for (const packName of selectedDependencyPacks(project, scaffold)) {
5192
+ const pack = scaffold.svadmin.dependencyPacks[packName];
5193
+ for (const [packageName, version] of Object.entries(pack)) {
5194
+ const existing = desired.get(packageName);
5195
+ if (existing !== undefined && (existing.version !== version || existing.section !== "dependencies")) {
5196
+ throw new Error(`scaffold dependency ${packageName} has conflicting canonical locations or versions`);
5197
+ }
5198
+ desired.set(packageName, { packageName, version, section: "dependencies" });
5199
+ }
5200
+ }
5201
+ return [...desired.values()];
5202
+ }
5203
+ function compareVersions(left, right) {
5204
+ if (left.major !== right.major)
5205
+ return left.major - right.major;
5206
+ if (left.minor !== right.minor)
5207
+ return left.minor - right.minor;
5208
+ return left.patch - right.patch;
5209
+ }
5210
+ function parseSimpleInterval(branch) {
5211
+ const match = /^([~^]?)(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z.-]+)?$/.exec(branch.trim());
5212
+ if (!match)
5213
+ return null;
5214
+ const operator = match[1];
5215
+ const minimum = {
5216
+ major: Number(match[2]),
5217
+ minor: Number(match[3]),
5218
+ patch: Number(match[4])
5219
+ };
5220
+ let maximumExclusive;
5221
+ if (operator === "^") {
5222
+ maximumExclusive = minimum.major > 0 ? { major: minimum.major + 1, minor: 0, patch: 0 } : minimum.minor > 0 ? { major: 0, minor: minimum.minor + 1, patch: 0 } : { major: 0, minor: 0, patch: minimum.patch + 1 };
5223
+ } else if (operator === "~") {
5224
+ maximumExclusive = { major: minimum.major, minor: minimum.minor + 1, patch: 0 };
5225
+ } else {
5226
+ maximumExclusive = { major: minimum.major, minor: minimum.minor, patch: minimum.patch + 1 };
5227
+ }
5228
+ return { minimum, maximumExclusive };
5229
+ }
5230
+ function parseSimpleRange(range) {
5231
+ const intervals = range.split("||").map(parseSimpleInterval);
5232
+ return intervals.some((interval) => interval === null) ? null : intervals.filter((interval) => interval !== null);
5233
+ }
5234
+ function intervalsOverlap(left, right) {
5235
+ return compareVersions(left.minimum, right.maximumExclusive) < 0 && compareVersions(right.minimum, left.maximumExclusive) < 0;
5236
+ }
5237
+ function rangesClearlyIncompatible(actual, expected) {
5238
+ const actualIntervals = parseSimpleRange(actual);
5239
+ const expectedIntervals = parseSimpleRange(expected);
5240
+ if (actualIntervals === null || expectedIntervals === null)
5241
+ return false;
5242
+ return !actualIntervals.some((actualInterval) => expectedIntervals.some((expectedInterval) => intervalsOverlap(actualInterval, expectedInterval)));
5243
+ }
5244
+ function alternateSection(section) {
5245
+ return section === "dependencies" ? "devDependencies" : "dependencies";
5246
+ }
5247
+ function dependencyState(project, desired) {
5248
+ const alternate = alternateSection(desired.section);
5249
+ return {
5250
+ canonicalVersion: project[desired.section]?.[desired.packageName],
5251
+ alternateVersion: project[alternate]?.[desired.packageName],
5252
+ alternateSection: alternate
5253
+ };
5254
+ }
5255
+ function upgradeAction(instruction) {
5256
+ return `${instruction} by running upgrade --write for this project`;
5257
+ }
5258
+ function missingDependencyIssue(desired) {
5259
+ return {
5260
+ kind: "missing",
5261
+ packageName: desired.packageName,
5262
+ expectedVersion: desired.version,
5263
+ expectedSection: desired.section,
5264
+ action: upgradeAction(`Add ${desired.packageName}@${desired.version} to ${desired.section}`)
5265
+ };
5266
+ }
5267
+ function misplacedDependencyIssue(desired, state) {
5268
+ return {
5269
+ kind: "section",
5270
+ packageName: desired.packageName,
5271
+ expectedVersion: desired.version,
5272
+ actualVersion: state.alternateVersion,
5273
+ expectedSection: desired.section,
5274
+ actualSection: state.alternateSection,
5275
+ action: upgradeAction(`Move ${desired.packageName} to ${desired.section}`)
5276
+ };
5277
+ }
5278
+ function versionDependencyIssue(desired, actualVersion) {
5279
+ const kind = rangesClearlyIncompatible(actualVersion, desired.version) ? "incompatible" : "drift";
5280
+ return {
5281
+ kind,
5282
+ packageName: desired.packageName,
5283
+ expectedVersion: desired.version,
5284
+ actualVersion,
5285
+ expectedSection: desired.section,
5286
+ action: upgradeAction(`Update ${desired.packageName} from ${actualVersion} to ${desired.version}`)
5287
+ };
5288
+ }
5289
+ function duplicateDependencyIssue(desired, state) {
5290
+ return {
5291
+ kind: "section",
5292
+ packageName: desired.packageName,
5293
+ expectedVersion: desired.version,
5294
+ actualVersion: state.alternateVersion,
5295
+ expectedSection: desired.section,
5296
+ actualSection: state.alternateSection,
5297
+ action: upgradeAction(`Remove the duplicate ${desired.packageName} entry from ${state.alternateSection}`)
5298
+ };
5299
+ }
5300
+ function doctorIssueForDependency(project, desired) {
5301
+ const state = dependencyState(project, desired);
5302
+ if (state.canonicalVersion === undefined) {
5303
+ return state.alternateVersion === undefined ? missingDependencyIssue(desired) : misplacedDependencyIssue(desired, state);
5304
+ }
5305
+ if (state.canonicalVersion !== desired.version) {
5306
+ return versionDependencyIssue(desired, state.canonicalVersion);
5307
+ }
5308
+ return state.alternateVersion === undefined ? null : duplicateDependencyIssue(desired, state);
5309
+ }
5310
+ function doctorProjectPackageJson(packageJsonCandidate, scaffold) {
5311
+ const project = parseMaintainedPackageJson(packageJsonCandidate);
5312
+ const issues = desiredDependencies(project, scaffold).map((desired) => doctorIssueForDependency(project, desired)).filter((issue) => issue !== null);
5313
+ return issues.length === 0 ? { status: "clean", exitCode: 0, issues: [] } : { status: "issues", exitCode: 1, issues };
5314
+ }
5315
+ function upgradeChangeForDependency(desired, state) {
5316
+ if (state.canonicalVersion === undefined && state.alternateVersion === undefined) {
5317
+ return { action: "add", packageName: desired.packageName, to: desired.version, section: desired.section };
5318
+ }
5319
+ if (state.canonicalVersion === undefined) {
5320
+ return {
5321
+ action: "move",
5322
+ packageName: desired.packageName,
5323
+ from: state.alternateVersion,
5324
+ to: desired.version,
5325
+ section: desired.section,
5326
+ previousSection: state.alternateSection
5327
+ };
5328
+ }
5329
+ if (state.canonicalVersion === desired.version && state.alternateVersion === undefined)
5330
+ return null;
5331
+ return {
5332
+ action: state.alternateVersion === undefined ? "update" : "move",
5333
+ packageName: desired.packageName,
5334
+ from: state.canonicalVersion,
5335
+ to: desired.version,
5336
+ section: desired.section,
5337
+ previousSection: state.alternateVersion === undefined ? undefined : state.alternateSection
5338
+ };
5339
+ }
5340
+ function applyDesiredDependency(updatedPackageJson, desired) {
5341
+ const canonicalDependencies = updatedPackageJson[desired.section] ?? {};
5342
+ const otherSection = alternateSection(desired.section);
5343
+ const alternateDependencies = updatedPackageJson[otherSection] ?? {};
5344
+ canonicalDependencies[desired.packageName] = desired.version;
5345
+ updatedPackageJson[desired.section] = canonicalDependencies;
5346
+ updatedPackageJson[otherSection] = Object.fromEntries(Object.entries(alternateDependencies).filter(([packageName]) => packageName !== desired.packageName));
5347
+ }
5348
+ function planProjectUpgrade(packageJsonCandidate, scaffold) {
5349
+ const project = parseMaintainedPackageJson(packageJsonCandidate);
5350
+ const updatedPackageJson = structuredClone(project);
5351
+ updatedPackageJson.dependencies = { ...project.dependencies ?? {} };
5352
+ updatedPackageJson.devDependencies = { ...project.devDependencies ?? {} };
5353
+ const changes = [];
5354
+ for (const desired of desiredDependencies(project, scaffold)) {
5355
+ const change = upgradeChangeForDependency(desired, dependencyState(updatedPackageJson, desired));
5356
+ if (change !== null)
5357
+ changes.push(change);
5358
+ applyDesiredDependency(updatedPackageJson, desired);
5359
+ }
5360
+ changes.sort((left, right) => left.section.localeCompare(right.section) || left.packageName.localeCompare(right.packageName));
5361
+ return { changes, updatedPackageJson };
5362
+ }
5363
+ function timestampForBackup(date) {
5364
+ return date.toISOString().replace(/[-:.]/g, "");
5365
+ }
5366
+ function createBackup(packagePath, timestamp) {
5367
+ for (let suffix = 0;suffix < 1000; suffix++) {
5368
+ const suffixText = suffix === 0 ? "" : `-${suffix}`;
5369
+ const backupPath = `${packagePath}.svadmin-backup-${timestamp}${suffixText}`;
5370
+ try {
5371
+ copyFileSync(packagePath, backupPath, constants.COPYFILE_EXCL);
5372
+ return backupPath;
5373
+ } catch (error) {
5374
+ if (error.code !== "EEXIST")
5375
+ throw error;
5376
+ }
5377
+ }
5378
+ throw new Error(`Unable to allocate a unique backup path for ${packagePath}`);
5379
+ }
5380
+ function writePackageJsonAtomically(packagePath, updatedPackageJson, timestamp) {
5381
+ const temporaryPath = `${packagePath}.svadmin-tmp-${process.pid}-${timestamp}`;
5382
+ try {
5383
+ writeFileSync(temporaryPath, `${JSON.stringify(updatedPackageJson, null, 2)}
5384
+ `, {
5385
+ encoding: "utf8",
5386
+ flag: "wx"
5387
+ });
5388
+ renameSync(temporaryPath, packagePath);
5389
+ } catch (error) {
5390
+ try {
5391
+ unlinkSync(temporaryPath);
5392
+ } catch (cleanupError) {
5393
+ if (cleanupError.code !== "ENOENT")
5394
+ throw cleanupError;
5395
+ }
5396
+ throw error;
5397
+ }
5398
+ }
5399
+ function planProjectPackageFileUpgrade(packagePath, scaffold) {
5400
+ const project = readMaintainedPackageJson(packagePath);
5401
+ const plan = planProjectUpgrade(project, scaffold);
5402
+ return { wrote: false, backupPath: null, plan };
5403
+ }
5404
+ function writeProjectPackageJsonUpgrade(packagePath, scaffold, backupDate) {
5405
+ const plannedUpgrade = planProjectPackageFileUpgrade(packagePath, scaffold);
5406
+ if (plannedUpgrade.plan.changes.length === 0)
5407
+ return plannedUpgrade;
5408
+ const timestamp = timestampForBackup(backupDate);
5409
+ const backupPath = createBackup(packagePath, timestamp);
5410
+ writePackageJsonAtomically(packagePath, plannedUpgrade.plan.updatedPackageJson, timestamp);
5411
+ return { wrote: true, backupPath, plan: plannedUpgrade.plan };
5412
+ }
5413
+
5414
+ // src/index.ts
5030
5415
  var __filename2 = fileURLToPath(import.meta.url);
5031
5416
  var __dirname2 = path.dirname(__filename2);
5417
+ function loadShippedScaffoldManifest() {
5418
+ return loadScaffoldManifest(path.join(__dirname2, "..", "scaffold-manifest.json"));
5419
+ }
5420
+ function projectDirectoryFromArguments(positional) {
5421
+ if (positional.length > 1) {
5422
+ throw new Error(`Expected at most one project directory, received: ${positional.join(", ")}`);
5423
+ }
5424
+ return path.resolve(process.cwd(), positional[0] ?? ".");
5425
+ }
5426
+ function doctorProjectDirectory(args) {
5427
+ const unknownOption = args.find((argument) => argument.startsWith("-"));
5428
+ if (unknownOption !== undefined)
5429
+ throw new Error(`Unknown option: ${unknownOption}`);
5430
+ return projectDirectoryFromArguments(args);
5431
+ }
5432
+ function parseUpgradeArguments(args) {
5433
+ let write = false;
5434
+ const positional = [];
5435
+ for (const argument of args) {
5436
+ if (argument === "--write") {
5437
+ write = true;
5438
+ } else if (argument.startsWith("-")) {
5439
+ throw new Error(`Unknown option: ${argument}`);
5440
+ } else {
5441
+ positional.push(argument);
5442
+ }
5443
+ }
5444
+ return { projectDirectory: projectDirectoryFromArguments(positional), write };
5445
+ }
5446
+ function doctorIssueMessage(issue) {
5447
+ const actualDescription = issue.actualVersion === undefined ? "missing" : `${issue.actualVersion}${issue.actualSection ? ` in ${issue.actualSection}` : ""}`;
5448
+ return `${issue.packageName}: ${issue.kind}; expected ${issue.expectedVersion} in ` + `${issue.expectedSection}, found ${actualDescription}`;
5449
+ }
5450
+ function upgradeChangeMessage(change) {
5451
+ if (change.action === "add") {
5452
+ return `add ${change.packageName}@${change.to} to ${change.section}`;
5453
+ }
5454
+ if (change.action === "move") {
5455
+ return `move ${change.packageName} to ${change.section} and set ${change.to}`;
5456
+ }
5457
+ return `update ${change.packageName} from ${change.from ?? "missing"} to ${change.to}`;
5458
+ }
5459
+ function printDoctorIssue(issue) {
5460
+ const marker = issue.kind === "drift" || issue.kind === "section" ? import_picocolors.default.yellow(" ⚠") : import_picocolors.default.red(" ✗");
5461
+ console.log(`${marker} ${doctorIssueMessage(issue)}`);
5462
+ console.log(import_picocolors.default.dim(` → ${issue.action}`));
5463
+ }
5464
+ function printDoctorReport(report, projectDirectory) {
5465
+ console.log();
5466
+ console.log(import_picocolors.default.bold(`svadmin doctor — ${projectDirectory}`));
5467
+ if (report.status === "clean") {
5468
+ console.log(import_picocolors.default.green(" ✔ Dependencies match the shipped svadmin scaffold."));
5469
+ } else {
5470
+ for (const issue of report.issues)
5471
+ printDoctorIssue(issue);
5472
+ console.log();
5473
+ console.log(import_picocolors.default.yellow(` ${report.issues.length} actionable issue(s) found.`));
5474
+ }
5475
+ console.log();
5476
+ }
5477
+ function doctor(args) {
5478
+ const projectDirectory = doctorProjectDirectory(args);
5479
+ const project = readMaintainedPackageJson(path.join(projectDirectory, "package.json"));
5480
+ const report = doctorProjectPackageJson(project, loadShippedScaffoldManifest());
5481
+ printDoctorReport(report, projectDirectory);
5482
+ process.exitCode = report.exitCode;
5483
+ }
5484
+ function printUpgradeChanges(upgradeExecution) {
5485
+ for (const change of upgradeExecution.plan.changes) {
5486
+ console.log(` ${import_picocolors.default.cyan("•")} ${upgradeChangeMessage(change)}`);
5487
+ }
5488
+ console.log();
5489
+ }
5490
+ function printUpgradeOutcome(upgradeExecution, packagePath) {
5491
+ if (upgradeExecution.wrote) {
5492
+ console.log(import_picocolors.default.green(" ✔ package.json updated."));
5493
+ console.log(` Backup: ${import_picocolors.default.cyan(upgradeExecution.backupPath)}`);
5494
+ console.log(` Restore by copying the backup over: ${import_picocolors.default.cyan(packagePath)}`);
5495
+ } else {
5496
+ console.log(import_picocolors.default.yellow(" Dry run only; package.json was not changed."));
5497
+ console.log(" Re-run this command with --write to apply the plan.");
5498
+ }
5499
+ console.log();
5500
+ }
5501
+ function printUpgradeExecution(upgradeExecution, projectDirectory, packagePath) {
5502
+ console.log();
5503
+ console.log(import_picocolors.default.bold(`svadmin upgrade — ${projectDirectory}`));
5504
+ if (upgradeExecution.plan.changes.length === 0) {
5505
+ console.log(import_picocolors.default.green(" ✔ package.json already matches the shipped scaffold."));
5506
+ console.log();
5507
+ return;
5508
+ }
5509
+ printUpgradeChanges(upgradeExecution);
5510
+ printUpgradeOutcome(upgradeExecution, packagePath);
5511
+ }
5512
+ function upgrade(args) {
5513
+ const commandArguments = parseUpgradeArguments(args);
5514
+ const packagePath = path.join(commandArguments.projectDirectory, "package.json");
5515
+ const scaffoldManifest = loadShippedScaffoldManifest();
5516
+ const upgradeExecution = commandArguments.write ? writeProjectPackageJsonUpgrade(packagePath, scaffoldManifest, new Date) : planProjectPackageFileUpgrade(packagePath, scaffoldManifest);
5517
+ printUpgradeExecution(upgradeExecution, commandArguments.projectDirectory, packagePath);
5518
+ }
5032
5519
  async function init() {
5033
5520
  console.log();
5034
5521
  console.log(import_picocolors.default.cyan(" ╔═══════════════════════════════════╗"));
@@ -5096,6 +5583,7 @@ Operation cancelled.
5096
5583
  ${import_picocolors.default.bold("Scaffolding")} project in ${import_picocolors.default.green(projectDir)}...
5097
5584
  `);
5098
5585
  const templateDir = path.join(__dirname2, "..", "template");
5586
+ const scaffoldManifest = loadShippedScaffoldManifest();
5099
5587
  function copyDir(src, dest) {
5100
5588
  fs.mkdirSync(dest, { recursive: true });
5101
5589
  const entries = fs.readdirSync(src, { withFileTypes: true });
@@ -5113,52 +5601,13 @@ ${import_picocolors.default.bold("Scaffolding")} project in ${import_picocolors.
5113
5601
  copyDir(templateDir, projectDir);
5114
5602
  console.log(import_picocolors.default.green(" ✔") + " Template files copied");
5115
5603
  }
5116
- const packageJson = {
5117
- name: response.projectName,
5118
- version: "0.1.0",
5119
- private: true,
5120
- type: "module",
5121
- scripts: {
5122
- dev: "vite",
5123
- build: "vite build",
5124
- preview: "vite preview",
5125
- check: "svelte-check --tsconfig ./tsconfig.json"
5126
- },
5127
- dependencies: {
5128
- "@svadmin/core": "^0.31.0",
5129
- "@svadmin/ui": "^0.38.0",
5130
- "@tanstack/svelte-query": "^6.1.29",
5131
- "highlight.js": "^11.11.1",
5132
- "@lucide/svelte": "^1.17.0"
5133
- },
5134
- devDependencies: {
5135
- "@sveltejs/vite-plugin-svelte": "^7.0.0",
5136
- "@tailwindcss/vite": "^4.2.2",
5137
- svelte: "^5.55.1",
5138
- "svelte-check": "^4.4.8",
5139
- tailwindcss: "^4.2.2",
5140
- "tw-animate-css": "^1.4.0",
5141
- typescript: "^6.0.0",
5142
- vite: "^8.0.3"
5143
- }
5144
- };
5145
- const dpMap = {
5146
- "simple-rest": { "@svadmin/simple-rest": "^0.9.8", "@refinedev/simple-rest": "^5.0.0" },
5147
- supabase: { "@svadmin/supabase": "^0.11.3", "@supabase/supabase-js": "^2.101.1" },
5148
- graphql: { "@svadmin/graphql": "^0.9.9", "graphql-request": "^7.4.0", graphql: "^16.8.0" }
5149
- };
5150
- if (dpMap[response.dataProvider]) {
5151
- Object.assign(packageJson.dependencies, dpMap[response.dataProvider]);
5152
- }
5153
- if (response.authProvider === "supabase" && response.dataProvider !== "supabase") {
5154
- packageJson.dependencies["@svadmin/supabase"] = "^0.11.3";
5155
- packageJson.dependencies["@supabase/supabase-js"] = "^2.101.1";
5156
- }
5157
- if (response.authProvider === "jwt" && response.dataProvider !== "simple-rest") {
5158
- packageJson.dependencies["@svadmin/simple-rest"] = "^0.9.8";
5159
- packageJson.dependencies["@refinedev/simple-rest"] = "^5.0.0";
5160
- }
5161
- fs.writeFileSync(path.join(projectDir, "package.json"), JSON.stringify(packageJson, null, 2));
5604
+ const packageJson = createProjectPackageJson(scaffoldManifest, {
5605
+ projectName: response.projectName,
5606
+ dataProvider: response.dataProvider,
5607
+ authProvider: response.authProvider
5608
+ });
5609
+ fs.writeFileSync(path.join(projectDir, "package.json"), `${JSON.stringify(packageJson, null, 2)}
5610
+ `);
5162
5611
  console.log(import_picocolors.default.green(" ✔") + " package.json generated");
5163
5612
  fs.writeFileSync(path.join(projectDir, ".gitignore"), `node_modules
5164
5613
  dist
@@ -5193,13 +5642,10 @@ bun run dev
5193
5642
  console.log(`
5194
5643
  ${import_picocolors.default.bold("Installing dependencies...")}
5195
5644
  `);
5196
- const { execSync } = await import("child_process");
5197
- try {
5198
- execSync("bun install", { cwd: projectDir, stdio: "inherit" });
5199
- } catch {
5200
- try {
5201
- execSync("npm install", { cwd: projectDir, stdio: "inherit" });
5202
- } catch {
5645
+ const bunInstall = spawnSync("bun", ["install"], { cwd: projectDir, stdio: "inherit" });
5646
+ if (bunInstall.status !== 0) {
5647
+ const npmInstall = spawnSync("npm", ["install"], { cwd: projectDir, stdio: "inherit" });
5648
+ if (npmInstall.status !== 0) {
5203
5649
  console.log(import_picocolors.default.yellow("\n ⚠ Auto-install failed. Run `bun install` or `npm install` manually."));
5204
5650
  }
5205
5651
  }
@@ -5308,8 +5754,21 @@ async function eject(args) {
5308
5754
  console.log();
5309
5755
  }
5310
5756
  var [, , subcommand, ...rest] = process.argv;
5757
+ var runCommand = (command) => {
5758
+ Promise.resolve().then(command).catch((error) => {
5759
+ const message = error instanceof Error ? error.message : String(error);
5760
+ console.error(import_picocolors.default.red(`
5761
+ ✗ ${message}
5762
+ `));
5763
+ process.exitCode = 2;
5764
+ });
5765
+ };
5311
5766
  if (subcommand === "eject") {
5312
- eject(rest).catch(console.error);
5767
+ runCommand(() => eject(rest));
5768
+ } else if (subcommand === "doctor") {
5769
+ runCommand(() => doctor(rest));
5770
+ } else if (subcommand === "upgrade") {
5771
+ runCommand(() => upgrade(rest));
5313
5772
  } else {
5314
- init().catch(console.error);
5773
+ runCommand(init);
5315
5774
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svadmin/create",
3
- "version": "0.12.5",
3
+ "version": "0.13.1",
4
4
  "description": "Scaffolding tool for svadmin projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,10 +8,13 @@
8
8
  },
9
9
  "files": [
10
10
  "dist",
11
- "template"
11
+ "template",
12
+ "scaffold-manifest.json"
12
13
  ],
13
14
  "scripts": {
14
15
  "build": "bun build src/index.ts --outdir dist --target node --format esm",
16
+ "test": "bun test src",
17
+ "check:compat": "bun ../../scripts/check-create-svadmin-compat.ts",
15
18
  "sync-template": "bun src/sync-template.ts",
16
19
  "prepublishOnly": "bun run build"
17
20
  },
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "svadmin-app",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview",
10
+ "check": "svelte-check --tsconfig ./tsconfig.json"
11
+ },
12
+ "dependencies": {
13
+ "@svadmin/core": "^0.35.0",
14
+ "@svadmin/ui": "^0.41.1",
15
+ "@tanstack/svelte-query": "^6.1.38",
16
+ "highlight.js": "^11.11.1",
17
+ "@lucide/svelte": "^1.30.0"
18
+ },
19
+ "devDependencies": {
20
+ "@sveltejs/vite-plugin-svelte": "^7.3.0",
21
+ "@tailwindcss/vite": "^4.3.3",
22
+ "svelte": "^5.56.8",
23
+ "svelte-check": "^4.7.5",
24
+ "tailwindcss": "^4.3.3",
25
+ "tw-animate-css": "^1.4.0",
26
+ "typescript": "~6.0.3",
27
+ "vite": "^8.2.1"
28
+ },
29
+ "svadmin": {
30
+ "dependencyPacks": {
31
+ "refine-core": {
32
+ "@refinedev/core": "^5.0.12"
33
+ },
34
+ "simple-rest": {
35
+ "@svadmin/simple-rest": "^0.9.11",
36
+ "@refinedev/simple-rest": "^6.0.1"
37
+ },
38
+ "supabase": {
39
+ "@svadmin/supabase": "^0.11.8",
40
+ "@supabase/supabase-js": "^2.112.2",
41
+ "@refinedev/supabase": "^6.0.2"
42
+ },
43
+ "graphql": {
44
+ "@svadmin/graphql": "^0.9.11",
45
+ "@refinedev/graphql": "^8.0.1",
46
+ "graphql-request": "^7.4.0",
47
+ "graphql": "^16.8.0"
48
+ }
49
+ },
50
+ "dataProviders": {
51
+ "simple-rest": ["refine-core", "simple-rest"],
52
+ "supabase": ["refine-core", "supabase"],
53
+ "graphql": ["refine-core", "graphql"],
54
+ "none": []
55
+ },
56
+ "authProviders": {
57
+ "mock": [],
58
+ "jwt": ["refine-core", "simple-rest"],
59
+ "supabase": ["refine-core", "supabase"],
60
+ "none": []
61
+ }
62
+ }
63
+ }