@evcraddock/slug-cli 0.6.0 → 0.6.2
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 +3 -1
- package/dist/commands.js +194 -51
- package/dist/http.d.ts +0 -7
- package/dist/http.js +2 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ Create a standalone site with:
|
|
|
25
25
|
slug init ./my-site --name my-site --site-title "My Site"
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
Run `slug init --help` for template options.
|
|
28
|
+
Run `slug init --help` for template options. By default, `slug init` consumes the package-backed `@evcraddock/slug-template` default template. `--template-dir` and `--template-url` remain available for local or remote template testing, and `--template` reserves the selection path for future named or external template packages.
|
|
29
29
|
|
|
30
30
|
## Login and configuration
|
|
31
31
|
|
|
@@ -49,6 +49,8 @@ The CLI stores YAML config at `${XDG_CONFIG_HOME:-~/.config}/slug/config.yaml` b
|
|
|
49
49
|
|
|
50
50
|
Use `slug --site <name> site config show` to read supported runtime site configuration through the Slugkit API. Use `slug --site <name> site config set <field> <value>` to update supported fields such as `name`, `url`, `tagline`, `description`, `homepage.intro`, and `homepage.body`.
|
|
51
51
|
|
|
52
|
+
Use `slug --site <name> doctor` to check API compatibility and package metadata. Use `slug --site <name> upgrade` for advisory, non-destructive package update recommendations; it reports suggested dependency changes without rewriting site files.
|
|
53
|
+
|
|
52
54
|
Use `slug --site <name> post list`, `slug --site <name> post show <slug>`, `slug --site <name> post create`, `slug --site <name> post edit <slug>`, `slug --site <name> post publish <slug>`, `slug --site <name> post unpublish <slug>`, and `slug --site <name> post delete <slug>` to manage posts through the Slugkit posts API.
|
|
53
55
|
|
|
54
56
|
Use `slug tag list` to list tags and post usage counts through the Slugkit tags API.
|
package/dist/commands.js
CHANGED
|
@@ -4,6 +4,8 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { dirname, basename, extname, join, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { x as extractTarball } from "tar";
|
|
7
|
+
import { SLUGKIT_API_MAJOR_VERSION, SLUGKIT_API_NAME, compareSlugkitApiVersions, normalizeApiBaseUrl, readSlugkitApiMajorVersion, } from "@evcraddock/slug-core";
|
|
8
|
+
import { GENERATED_SITE_SLUGKIT_DEPENDENCIES, SLUGKIT_TEMPLATE_PACKAGE_NAME, getTemplateMetadata, getTemplateRoot, isTemplateEntryIncluded, } from "@evcraddock/slug-template";
|
|
7
9
|
import { isValidSiteName, readConfig, removeConfigSite, setConfigSiteApiBaseUrl, setConfigSiteApiKey, toDisplayConfig, writeConfig, } from "./config.js";
|
|
8
10
|
import { CliError, createInvalidUsageError, ExitCode } from "./errors.js";
|
|
9
11
|
import { SlugHttpClient } from "./http.js";
|
|
@@ -15,8 +17,9 @@ Usage:
|
|
|
15
17
|
slug [--config <file>] [--site <name>] --version
|
|
16
18
|
slug [--config <file>] [--site <name>] version [--json]
|
|
17
19
|
slug [--config <file>] --site <name> doctor [--json]
|
|
20
|
+
slug [--config <file>] --site <name> upgrade [--json]
|
|
18
21
|
slug [--config <file>] --site <name> login [api-base-url]
|
|
19
|
-
slug [--config <file>] init <directory> --name <name> [--site-title <title>] [--template-url <url>] [--template-dir <dir>] [--json]
|
|
22
|
+
slug [--config <file>] init <directory> --name <name> [--site-title <title>] [--template <name>] [--template-url <url>] [--template-dir <dir>] [--json]
|
|
20
23
|
slug [--config <file>] --site <name> post list [--type article|link|note] [--status draft|published|all] [--tag <slug>] [--json]
|
|
21
24
|
slug [--config <file>] post show <slug> [--json]
|
|
22
25
|
slug [--config <file>] post create --type article|link|note --slug <slug> --content <text> [--title <text>] [--url <url>] [--excerpt <text>] [--tag <slug>]... [--source-id <id>] [--credit-contact-id <id>]... [--json]
|
|
@@ -84,6 +87,8 @@ export async function runCommand(context) {
|
|
|
84
87
|
return runVersionCommand(context, args.slice(1));
|
|
85
88
|
case "doctor":
|
|
86
89
|
return runDoctorCommand(context, args.slice(1));
|
|
90
|
+
case "upgrade":
|
|
91
|
+
return runUpgradeCommand(context, args.slice(1));
|
|
87
92
|
case "login":
|
|
88
93
|
return runLoginCommand(context, args.slice(1));
|
|
89
94
|
case "init":
|
|
@@ -128,24 +133,25 @@ async function runVersionCommand(context, args) {
|
|
|
128
133
|
}
|
|
129
134
|
return { exitCode: ExitCode.Ok };
|
|
130
135
|
}
|
|
131
|
-
const SUPPORTED_API_MAJOR_VERSION =
|
|
132
|
-
const SITE_TEMPLATE_PACKAGE_NAME =
|
|
133
|
-
const
|
|
136
|
+
const SUPPORTED_API_MAJOR_VERSION = SLUGKIT_API_MAJOR_VERSION;
|
|
137
|
+
const SITE_TEMPLATE_PACKAGE_NAME = getTemplateMetadata().templateSitePackageName;
|
|
138
|
+
const DEFAULT_TEMPLATE_METADATA = getTemplateMetadata();
|
|
139
|
+
const DEFAULT_TEMPLATE_NAME = DEFAULT_TEMPLATE_METADATA.name;
|
|
134
140
|
const INIT_HELP_TEXT = `slug init - create a standalone Slugkit-compatible website
|
|
135
141
|
|
|
136
142
|
Usage:
|
|
137
|
-
slug init <directory> --name <name> [--site-title <title>] [--template-url <url>] [--template-dir <dir>] [--json]
|
|
143
|
+
slug init <directory> --name <name> [--site-title <title>] [--template <name>] [--template-url <url>] [--template-dir <dir>] [--json]
|
|
138
144
|
|
|
139
145
|
Options:
|
|
140
146
|
--name <name> npm package name for the generated site.
|
|
141
147
|
--site-title <title> Human-readable site title. Defaults to a title derived from --name.
|
|
148
|
+
--template <name> Template name or package. Defaults to ${DEFAULT_TEMPLATE_NAME}.
|
|
142
149
|
--template-url <url> Download a template tarball from a custom URL.
|
|
143
150
|
--template-dir <dir> Copy a template from a local directory.
|
|
144
151
|
--json Print command output as JSON.
|
|
145
152
|
--help, -h Show this help.
|
|
146
153
|
|
|
147
|
-
By default, slug init uses
|
|
148
|
-
Installed CLI releases download the versioned template asset that matches the CLI version.`;
|
|
154
|
+
By default, slug init uses ${SLUGKIT_TEMPLATE_PACKAGE_NAME}. Template packages are initialization sources only; generated sites do not depend on them at runtime.`;
|
|
149
155
|
const SITE_INIT_NEXT_STEPS = [
|
|
150
156
|
"Install dependencies",
|
|
151
157
|
"Copy and edit .env",
|
|
@@ -155,14 +161,6 @@ const SITE_INIT_NEXT_STEPS = [
|
|
|
155
161
|
"Configure ActivityPub domain and actor settings",
|
|
156
162
|
"Configure slug and run slug doctor",
|
|
157
163
|
];
|
|
158
|
-
const TEMPLATE_COPY_EXCLUDES = new Set([
|
|
159
|
-
".env",
|
|
160
|
-
"node_modules",
|
|
161
|
-
"dist",
|
|
162
|
-
"build",
|
|
163
|
-
"data",
|
|
164
|
-
"coverage",
|
|
165
|
-
]);
|
|
166
164
|
async function runInitCommand(context, args) {
|
|
167
165
|
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
168
166
|
context.writer.stdout(INIT_HELP_TEXT);
|
|
@@ -205,6 +203,7 @@ function parseInitArgs(args) {
|
|
|
205
203
|
let directory;
|
|
206
204
|
let name;
|
|
207
205
|
let siteTitle;
|
|
206
|
+
let template;
|
|
208
207
|
let templateUrl;
|
|
209
208
|
let templateDir;
|
|
210
209
|
let json = false;
|
|
@@ -224,6 +223,11 @@ function parseInitArgs(args) {
|
|
|
224
223
|
index += 1;
|
|
225
224
|
continue;
|
|
226
225
|
}
|
|
226
|
+
if (arg === "--template") {
|
|
227
|
+
template = readRequiredInitOptionValue(args, index, "template");
|
|
228
|
+
index += 1;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
227
231
|
if (arg === "--template-url") {
|
|
228
232
|
templateUrl = readRequiredInitOptionValue(args, index, "template-url");
|
|
229
233
|
index += 1;
|
|
@@ -238,15 +242,18 @@ function parseInitArgs(args) {
|
|
|
238
242
|
throw createInvalidUsageError(`Unsupported option: ${arg}`);
|
|
239
243
|
}
|
|
240
244
|
if (directory !== undefined || arg === undefined) {
|
|
241
|
-
throw createInvalidUsageError("Usage: slug init <directory> --name <name> [--site-title <title>] [--template-url <url>] [--template-dir <dir>] [--json]");
|
|
245
|
+
throw createInvalidUsageError("Usage: slug init <directory> --name <name> [--site-title <title>] [--template <name>] [--template-url <url>] [--template-dir <dir>] [--json]");
|
|
242
246
|
}
|
|
243
247
|
directory = arg;
|
|
244
248
|
}
|
|
245
249
|
if (directory === undefined || name === undefined) {
|
|
246
|
-
throw createInvalidUsageError("Usage: slug init <directory> --name <name> [--site-title <title>] [--template-url <url>] [--template-dir <dir>] [--json]");
|
|
250
|
+
throw createInvalidUsageError("Usage: slug init <directory> --name <name> [--site-title <title>] [--template <name>] [--template-url <url>] [--template-dir <dir>] [--json]");
|
|
247
251
|
}
|
|
248
252
|
const normalizedName = name.trim();
|
|
249
253
|
validatePackageName(normalizedName);
|
|
254
|
+
if (template !== undefined && (templateUrl !== undefined || templateDir !== undefined)) {
|
|
255
|
+
throw createInvalidUsageError("Use --template, --template-url, or --template-dir, not more than one");
|
|
256
|
+
}
|
|
250
257
|
if (templateUrl !== undefined && templateDir !== undefined) {
|
|
251
258
|
throw createInvalidUsageError("Use only one of --template-url or --template-dir");
|
|
252
259
|
}
|
|
@@ -255,6 +262,7 @@ function parseInitArgs(args) {
|
|
|
255
262
|
name: normalizedName,
|
|
256
263
|
siteTitle: siteTitle?.trim() || humanizePackageName(normalizedName),
|
|
257
264
|
json,
|
|
265
|
+
template: template?.trim() || undefined,
|
|
258
266
|
templateUrl: templateUrl?.trim() || undefined,
|
|
259
267
|
templateDir: templateDir?.trim() || undefined,
|
|
260
268
|
};
|
|
@@ -323,20 +331,15 @@ async function copyTemplateSite(source, destination, relativeDirectory = "") {
|
|
|
323
331
|
}
|
|
324
332
|
}
|
|
325
333
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
join("src", "federation", "__tests__", "follow.test.ts"),
|
|
329
|
-
]);
|
|
330
|
-
function shouldExcludeTemplateEntry(relativePath, name) {
|
|
331
|
-
return (GENERATED_SITE_TEST_EXCLUDES.has(relativePath) ||
|
|
332
|
-
TEMPLATE_COPY_EXCLUDES.has(name) ||
|
|
333
|
-
name.endsWith(".log") ||
|
|
334
|
-
name.endsWith(".tsbuildinfo"));
|
|
334
|
+
function shouldExcludeTemplateEntry(relativePath, _name) {
|
|
335
|
+
return !isTemplateEntryIncluded(relativePath);
|
|
335
336
|
}
|
|
336
337
|
async function replaceGeneratedSitePlaceholders(directory, options) {
|
|
337
338
|
await updateJsonFile(join(directory, "package.json"), (value) => ({
|
|
338
339
|
...value,
|
|
339
340
|
name: options.name,
|
|
341
|
+
dependencies: replaceGeneratedSiteWorkspaceDependencies(value.dependencies),
|
|
342
|
+
devDependencies: replaceGeneratedSiteWorkspaceDependencies(value.devDependencies),
|
|
340
343
|
}));
|
|
341
344
|
await replaceTextFile(join(directory, "src", "config", "site.ts"), [
|
|
342
345
|
['name: "slugkit"', `name: ${JSON.stringify(options.siteTitle)}`],
|
|
@@ -365,6 +368,16 @@ async function replaceGeneratedSitePlaceholders(directory, options) {
|
|
|
365
368
|
],
|
|
366
369
|
]);
|
|
367
370
|
}
|
|
371
|
+
function replaceGeneratedSiteWorkspaceDependencies(value) {
|
|
372
|
+
if (!isRecord(value))
|
|
373
|
+
return value;
|
|
374
|
+
return Object.fromEntries(Object.entries(value).map(([name, version]) => [
|
|
375
|
+
name,
|
|
376
|
+
version === "workspace:*" && GENERATED_SITE_SLUGKIT_DEPENDENCIES[name] !== undefined
|
|
377
|
+
? GENERATED_SITE_SLUGKIT_DEPENDENCIES[name]
|
|
378
|
+
: version,
|
|
379
|
+
]));
|
|
380
|
+
}
|
|
368
381
|
async function updateJsonFile(path, update) {
|
|
369
382
|
const value = JSON.parse(await readFile(path, "utf8"));
|
|
370
383
|
await writeFile(path, `${JSON.stringify(update(value), null, 2)}\n`, "utf8");
|
|
@@ -381,11 +394,24 @@ async function writeGeneratedSiteMarker(directory, options) {
|
|
|
381
394
|
slugkitSite: true,
|
|
382
395
|
name: options.name,
|
|
383
396
|
siteTitle: options.siteTitle,
|
|
384
|
-
template:
|
|
397
|
+
template: `${SLUGKIT_TEMPLATE_PACKAGE_NAME}/${DEFAULT_TEMPLATE_NAME}`,
|
|
385
398
|
generatedAt: new Date().toISOString(),
|
|
399
|
+
packageMetadata: createGeneratedSitePackageMetadata(DEFAULT_TEMPLATE_METADATA),
|
|
386
400
|
};
|
|
387
401
|
await writeFile(join(directory, ".slugkit-site.json"), `${JSON.stringify(marker, null, 2)}\n`, "utf8");
|
|
388
402
|
}
|
|
403
|
+
function createGeneratedSitePackageMetadata(metadata) {
|
|
404
|
+
return {
|
|
405
|
+
template: {
|
|
406
|
+
packageName: metadata.packageName,
|
|
407
|
+
name: metadata.name,
|
|
408
|
+
version: metadata.version,
|
|
409
|
+
},
|
|
410
|
+
packages: { ...metadata.includedSlugkitPackages },
|
|
411
|
+
advisoryOnly: true,
|
|
412
|
+
migrationNotesUrl: "https://slugkit.com/docs/package-updates",
|
|
413
|
+
};
|
|
414
|
+
}
|
|
389
415
|
async function resolveInitTemplateSource(context, options) {
|
|
390
416
|
if (options.templateDir !== undefined) {
|
|
391
417
|
const directory = resolve(options.templateDir);
|
|
@@ -395,20 +421,20 @@ async function resolveInitTemplateSource(context, options) {
|
|
|
395
421
|
if (options.templateUrl !== undefined) {
|
|
396
422
|
return downloadTemplateSite(context, options.templateUrl);
|
|
397
423
|
}
|
|
424
|
+
if (options.template !== undefined) {
|
|
425
|
+
return resolveNamedTemplateSource(options.template);
|
|
426
|
+
}
|
|
398
427
|
const localTemplateDirectory = findTemplateSiteDirectory(context.templateSiteDirectory);
|
|
399
428
|
if (localTemplateDirectory !== undefined) {
|
|
400
429
|
return { directory: localTemplateDirectory };
|
|
401
430
|
}
|
|
402
|
-
return
|
|
403
|
-
}
|
|
404
|
-
function getDefaultTemplateAssetUrl(context) {
|
|
405
|
-
const version = context.packageVersion.trim().replace(/^v/, "");
|
|
406
|
-
const baseUrl = context.templateAssetBaseUrl ?? SITE_TEMPLATE_RELEASE_BASE_URL;
|
|
407
|
-
const assetName = getTemplateAssetName(version);
|
|
408
|
-
return `${baseUrl.replace(/\/$/u, "")}/v${encodeURIComponent(version)}/${encodeURIComponent(assetName)}`;
|
|
431
|
+
return { directory: getTemplateRoot() };
|
|
409
432
|
}
|
|
410
|
-
function
|
|
411
|
-
|
|
433
|
+
function resolveNamedTemplateSource(template) {
|
|
434
|
+
if (template === DEFAULT_TEMPLATE_NAME || template === SLUGKIT_TEMPLATE_PACKAGE_NAME) {
|
|
435
|
+
return { directory: getTemplateRoot() };
|
|
436
|
+
}
|
|
437
|
+
throw createInvalidUsageError(`Unsupported template: ${template}. This CLI currently includes ${DEFAULT_TEMPLATE_NAME}; future releases may resolve external template packages.`);
|
|
412
438
|
}
|
|
413
439
|
async function downloadTemplateSite(context, templateUrl) {
|
|
414
440
|
let parsedUrl;
|
|
@@ -496,6 +522,7 @@ async function runDoctorCommand(context, args) {
|
|
|
496
522
|
checks.push(createHealthCheck(health));
|
|
497
523
|
const meta = await requestDoctorJson(context, apiBaseUrl, "/meta");
|
|
498
524
|
checks.push(createMetaCheck(meta));
|
|
525
|
+
checks.push(createPackageMetadataCheck(meta));
|
|
499
526
|
const openapi = await requestDoctorJson(context, apiBaseUrl, "/openapi.json");
|
|
500
527
|
checks.push(createOpenApiCheck(openapi));
|
|
501
528
|
if (config.apiKey === undefined || config.apiKey.trim() === "") {
|
|
@@ -595,10 +622,10 @@ function createMetaCheck(result) {
|
|
|
595
622
|
if (!isMetaDocument(result.data)) {
|
|
596
623
|
return { name: "meta", status: "fail", message: "Metadata response is invalid" };
|
|
597
624
|
}
|
|
598
|
-
if (result.data.api.name !==
|
|
625
|
+
if (result.data.api.name !== SLUGKIT_API_NAME) {
|
|
599
626
|
return { name: "meta", status: "fail", message: "API name is not slugkit" };
|
|
600
627
|
}
|
|
601
|
-
if (
|
|
628
|
+
if (readSlugkitApiMajorVersion(result.data.api.version) !== SUPPORTED_API_MAJOR_VERSION) {
|
|
602
629
|
return {
|
|
603
630
|
name: "meta",
|
|
604
631
|
status: "fail",
|
|
@@ -607,6 +634,35 @@ function createMetaCheck(result) {
|
|
|
607
634
|
}
|
|
608
635
|
return { name: "meta", status: "pass", message: `API version ${result.data.api.version}` };
|
|
609
636
|
}
|
|
637
|
+
function createPackageMetadataCheck(result) {
|
|
638
|
+
if (!result.ok) {
|
|
639
|
+
return { name: "packages", status: "warn", message: "Package metadata is unavailable" };
|
|
640
|
+
}
|
|
641
|
+
if (!isMetaDocument(result.data) || result.data.packageMetadata === undefined) {
|
|
642
|
+
return {
|
|
643
|
+
name: "packages",
|
|
644
|
+
status: "warn",
|
|
645
|
+
message: "Package metadata is not exposed by this site",
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
const metadata = result.data.packageMetadata;
|
|
649
|
+
const stalePackages = findStaleSlugkitPackages(metadata.packages);
|
|
650
|
+
if (stalePackages.length > 0) {
|
|
651
|
+
return {
|
|
652
|
+
name: "packages",
|
|
653
|
+
status: "warn",
|
|
654
|
+
message: `Updates available: ${stalePackages
|
|
655
|
+
.map((entry) => `${entry.packageName} ${entry.currentVersion} → ${entry.recommendedVersion}`)
|
|
656
|
+
.join(", ")}`,
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
const packageCount = Object.keys(metadata.packages).length;
|
|
660
|
+
return {
|
|
661
|
+
name: "packages",
|
|
662
|
+
status: "pass",
|
|
663
|
+
message: `${packageCount} Slugkit package versions reported; upgrades are advisory`,
|
|
664
|
+
};
|
|
665
|
+
}
|
|
610
666
|
function createOpenApiCheck(result) {
|
|
611
667
|
if (!result.ok) {
|
|
612
668
|
return { name: "openapi", status: "fail", message: result.message };
|
|
@@ -645,13 +701,108 @@ function getDoctorExitCode(checks) {
|
|
|
645
701
|
}
|
|
646
702
|
return ExitCode.ApiError;
|
|
647
703
|
}
|
|
704
|
+
async function runUpgradeCommand(context, args) {
|
|
705
|
+
const json = readJsonFlag(args);
|
|
706
|
+
const siteResult = await readDoctorSiteConfig(context);
|
|
707
|
+
if (!siteResult.ok) {
|
|
708
|
+
const result = {
|
|
709
|
+
ok: false,
|
|
710
|
+
advisoryOnly: true,
|
|
711
|
+
message: siteResult.message,
|
|
712
|
+
recommendations: [],
|
|
713
|
+
};
|
|
714
|
+
return finishUpgrade(context, result, json, ExitCode.InvalidUsage);
|
|
715
|
+
}
|
|
716
|
+
const apiBaseUrl = normalizeUrl(siteResult.site.apiBaseUrl);
|
|
717
|
+
const meta = await requestDoctorJson(context, apiBaseUrl, "/meta");
|
|
718
|
+
if (!meta.ok) {
|
|
719
|
+
const result = {
|
|
720
|
+
ok: false,
|
|
721
|
+
advisoryOnly: true,
|
|
722
|
+
message: meta.message,
|
|
723
|
+
recommendations: [],
|
|
724
|
+
};
|
|
725
|
+
return finishUpgrade(context, result, json, getDoctorExitCode([{ name: "meta", status: "fail", message: meta.message }]));
|
|
726
|
+
}
|
|
727
|
+
if (!isMetaDocument(meta.data) || meta.data.packageMetadata === undefined) {
|
|
728
|
+
const result = {
|
|
729
|
+
ok: true,
|
|
730
|
+
advisoryOnly: true,
|
|
731
|
+
message: "Package metadata is unavailable; no files were changed.",
|
|
732
|
+
recommendations: [],
|
|
733
|
+
};
|
|
734
|
+
return finishUpgrade(context, result, json, ExitCode.Ok);
|
|
735
|
+
}
|
|
736
|
+
const metadata = meta.data.packageMetadata;
|
|
737
|
+
const recommendations = findStaleSlugkitPackages(metadata.packages);
|
|
738
|
+
const result = {
|
|
739
|
+
ok: true,
|
|
740
|
+
advisoryOnly: true,
|
|
741
|
+
message: recommendations.length === 0
|
|
742
|
+
? "No Slugkit package updates are currently recommended. No files were changed."
|
|
743
|
+
: "Slugkit package updates are available. Review and apply them manually; no files were changed.",
|
|
744
|
+
recommendations,
|
|
745
|
+
migrationNotesUrl: metadata.migrationNotesUrl,
|
|
746
|
+
};
|
|
747
|
+
if (metadata.template !== undefined) {
|
|
748
|
+
result.template = `${metadata.template.packageName}/${metadata.template.name}@${metadata.template.version}`;
|
|
749
|
+
}
|
|
750
|
+
return finishUpgrade(context, result, json, ExitCode.Ok);
|
|
751
|
+
}
|
|
752
|
+
function finishUpgrade(context, result, json, exitCode) {
|
|
753
|
+
if (json) {
|
|
754
|
+
writeJson(context.writer, result);
|
|
755
|
+
}
|
|
756
|
+
else {
|
|
757
|
+
context.writer.stdout("Slug upgrade: advisory only");
|
|
758
|
+
context.writer.stdout(result.message);
|
|
759
|
+
if (result.template !== undefined) {
|
|
760
|
+
context.writer.stdout(`Template: ${result.template}`);
|
|
761
|
+
}
|
|
762
|
+
if (result.recommendations.length > 0) {
|
|
763
|
+
context.writer.stdout("Recommended package updates:");
|
|
764
|
+
for (const recommendation of result.recommendations) {
|
|
765
|
+
context.writer.stdout(`- ${recommendation.packageName}: ${recommendation.currentVersion} -> ${recommendation.recommendedVersion}`);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
if (result.migrationNotesUrl !== undefined) {
|
|
769
|
+
context.writer.stdout(`Migration notes: ${result.migrationNotesUrl}`);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
return { exitCode };
|
|
773
|
+
}
|
|
648
774
|
function isMetaDocument(value) {
|
|
649
775
|
return (isRecord(value) &&
|
|
650
776
|
isRecord(value.api) &&
|
|
651
777
|
typeof value.api.name === "string" &&
|
|
652
778
|
typeof value.api.version === "string" &&
|
|
653
779
|
typeof value.api.baseUrl === "string" &&
|
|
654
|
-
typeof value.api.openapiUrl === "string"
|
|
780
|
+
typeof value.api.openapiUrl === "string" &&
|
|
781
|
+
(value.packageMetadata === undefined || isPackageMetadata(value.packageMetadata)));
|
|
782
|
+
}
|
|
783
|
+
function isPackageMetadata(value) {
|
|
784
|
+
return (isRecord(value) &&
|
|
785
|
+
isRecord(value.packages) &&
|
|
786
|
+
value.advisoryOnly === true &&
|
|
787
|
+
Object.values(value.packages).every((version) => typeof version === "string") &&
|
|
788
|
+
(value.template === undefined ||
|
|
789
|
+
(isRecord(value.template) &&
|
|
790
|
+
typeof value.template.packageName === "string" &&
|
|
791
|
+
typeof value.template.name === "string" &&
|
|
792
|
+
typeof value.template.version === "string")) &&
|
|
793
|
+
(value.migrationNotesUrl === undefined || typeof value.migrationNotesUrl === "string"));
|
|
794
|
+
}
|
|
795
|
+
function findStaleSlugkitPackages(packages) {
|
|
796
|
+
return Object.entries(GENERATED_SITE_SLUGKIT_DEPENDENCIES)
|
|
797
|
+
.map(([packageName, recommendedVersion]) => {
|
|
798
|
+
const currentVersion = packages[packageName];
|
|
799
|
+
if (currentVersion === undefined)
|
|
800
|
+
return undefined;
|
|
801
|
+
if (compareSlugkitApiVersions(currentVersion, recommendedVersion) >= 0)
|
|
802
|
+
return undefined;
|
|
803
|
+
return { packageName, currentVersion, recommendedVersion };
|
|
804
|
+
})
|
|
805
|
+
.filter((entry) => entry !== undefined);
|
|
655
806
|
}
|
|
656
807
|
function isOpenApiDocument(value) {
|
|
657
808
|
return (isRecord(value) &&
|
|
@@ -663,10 +814,6 @@ function isOpenApiDocument(value) {
|
|
|
663
814
|
function isRecord(value) {
|
|
664
815
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
665
816
|
}
|
|
666
|
-
function readMajorVersion(value) {
|
|
667
|
-
const major = Number.parseInt(value.split(".")[0] ?? "", 10);
|
|
668
|
-
return Number.isInteger(major) ? major : undefined;
|
|
669
|
-
}
|
|
670
817
|
function readApiErrorMessage(value) {
|
|
671
818
|
if (!isRecord(value) || !isRecord(value.error) || typeof value.error.message !== "string") {
|
|
672
819
|
return undefined;
|
|
@@ -2336,17 +2483,13 @@ export function createBrowserAuthUrl(apiBaseUrl) {
|
|
|
2336
2483
|
return url.toString();
|
|
2337
2484
|
}
|
|
2338
2485
|
function normalizeUrl(value) {
|
|
2339
|
-
validateUrl(value);
|
|
2340
|
-
return value.replace(/\/+$/, "");
|
|
2341
|
-
}
|
|
2342
|
-
function validateUrl(value) {
|
|
2343
2486
|
try {
|
|
2344
|
-
|
|
2345
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
2346
|
-
throw new Error("Unsupported protocol");
|
|
2347
|
-
}
|
|
2487
|
+
return normalizeApiBaseUrl(value);
|
|
2348
2488
|
}
|
|
2349
2489
|
catch {
|
|
2350
2490
|
throw createInvalidUsageError("api-base-url must be a valid HTTP or HTTPS URL");
|
|
2351
2491
|
}
|
|
2352
2492
|
}
|
|
2493
|
+
function validateUrl(value) {
|
|
2494
|
+
normalizeUrl(value);
|
|
2495
|
+
}
|
package/dist/http.d.ts
CHANGED
|
@@ -8,13 +8,6 @@ export interface HttpRequestOptions {
|
|
|
8
8
|
path: string;
|
|
9
9
|
body?: unknown;
|
|
10
10
|
}
|
|
11
|
-
export interface ApiErrorBody {
|
|
12
|
-
error?: {
|
|
13
|
-
code?: string;
|
|
14
|
-
message?: string;
|
|
15
|
-
operation?: string;
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
11
|
export declare class SlugHttpClient {
|
|
19
12
|
private readonly apiBaseUrl;
|
|
20
13
|
private readonly apiKey;
|
package/dist/http.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { normalizeApiBaseUrl } from "@evcraddock/slug-core";
|
|
1
2
|
import { CliError, ExitCode } from "./errors.js";
|
|
2
3
|
export class SlugHttpClient {
|
|
3
4
|
apiBaseUrl;
|
|
4
5
|
apiKey;
|
|
5
6
|
fetchImpl;
|
|
6
7
|
constructor(options) {
|
|
7
|
-
this.apiBaseUrl = options.apiBaseUrl
|
|
8
|
+
this.apiBaseUrl = normalizeApiBaseUrl(options.apiBaseUrl);
|
|
8
9
|
this.apiKey = options.apiKey;
|
|
9
10
|
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
10
11
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evcraddock/slug-cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Command-line tool for Slugkit sites.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
"test:watch": "vitest --config ../vitest.config.ts src"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
+
"@evcraddock/slug-core": "0.1.1",
|
|
30
|
+
"@evcraddock/slug-template": "0.1.2",
|
|
29
31
|
"tar": "^7.5.16"
|
|
30
32
|
}
|
|
31
33
|
}
|