@evcraddock/slug-cli 0.6.0 → 0.6.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
@@ -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. In repository-local development, `slug init` copies `template/site` directly. Installed CLI releases download the matching versioned template asset from the Forgejo release unless `--template-url` or `--template-dir` is provided.
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 = 1;
132
- const SITE_TEMPLATE_PACKAGE_NAME = "@slugkit/template-site";
133
- const SITE_TEMPLATE_RELEASE_BASE_URL = "https://forge.caradoc.com/erik/slugkit/releases/download";
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 the repository-local template/site directory when available.
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
- const GENERATED_SITE_TEST_EXCLUDES = new Set([
327
- join("src", "__tests__", "dev-environment.test.ts"),
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: "slugkit/template-site",
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 downloadTemplateSite(context, getDefaultTemplateAssetUrl(context));
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 getTemplateAssetName(version) {
411
- return `slugkit-site-template-v${version}.tgz`;
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 !== "slugkit") {
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 (readMajorVersion(result.data.api.version) !== SUPPORTED_API_MAJOR_VERSION) {
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
- const url = new URL(value);
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.replace(/\/$/, "");
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.0",
3
+ "version": "0.6.1",
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.1",
29
31
  "tar": "^7.5.16"
30
32
  }
31
33
  }