@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 +22 -0
- package/dist/index.js +514 -55
- package/package.json +5 -2
- package/scaffold-manifest.json +63 -0
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
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
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 {
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
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)
|
|
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
|
|
5773
|
+
runCommand(init);
|
|
5315
5774
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svadmin/create",
|
|
3
|
-
"version": "0.
|
|
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
|
+
}
|