@evcraddock/slug-cli 0.6.3 → 0.6.4

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. 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.
28
+ Run `slug init --help` for template options. By default, `slug init` downloads the default template zip artifact. `--template-dir` remains available for local template development, `--template-url` supports alternate remote zip artifacts, and `--template` reserves the selection path for future named templates.
29
29
 
30
30
  ## Login and configuration
31
31
 
@@ -9,7 +9,6 @@ export interface CommandContext {
9
9
  fetchImpl?: typeof fetch;
10
10
  openUrl?: (url: string) => Promise<void> | void;
11
11
  prompt?: (message: string) => Promise<string> | string;
12
- templateSiteDirectory?: string | null;
13
12
  templateAssetBaseUrl?: string;
14
13
  }
15
14
  export interface CommandResult {
package/dist/commands.js CHANGED
@@ -1,11 +1,8 @@
1
- import { readFileSync } from "node:fs";
2
1
  import { mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
3
2
  import { tmpdir } from "node:os";
4
3
  import { dirname, basename, extname, join, resolve } from "node:path";
5
- import { fileURLToPath } from "node:url";
6
- import { x as extractTarball } from "tar";
4
+ import AdmZip from "adm-zip";
7
5
  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";
9
6
  import { isValidSiteName, readConfig, removeConfigSite, setConfigSiteApiBaseUrl, setConfigSiteApiKey, toDisplayConfig, writeConfig, } from "./config.js";
10
7
  import { CliError, createInvalidUsageError, ExitCode } from "./errors.js";
11
8
  import { SlugHttpClient } from "./http.js";
@@ -134,9 +131,16 @@ async function runVersionCommand(context, args) {
134
131
  return { exitCode: ExitCode.Ok };
135
132
  }
136
133
  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
+ const SITE_TEMPLATE_PACKAGE_NAME = "@slugkit/template-site";
135
+ const DEFAULT_TEMPLATE_NAME = "default";
136
+ const DEFAULT_TEMPLATE_URL = "https://forge.caradoc.com/erik/slugkit/releases/download/slugkit-template-latest/slugkit-site-template.zip";
137
+ const GENERATED_SITE_SLUGKIT_DEPENDENCIES = {
138
+ "@evcraddock/slug-api": "0.1.1",
139
+ "@evcraddock/slug-auth": "0.1.0",
140
+ "@evcraddock/slug-core": "0.1.1",
141
+ "@evcraddock/slug-federation": "0.1.0",
142
+ "@evcraddock/slug-media": "0.1.0",
143
+ };
140
144
  const INIT_HELP_TEXT = `slug init - create a standalone Slugkit-compatible website
141
145
 
142
146
  Usage:
@@ -146,12 +150,12 @@ Options:
146
150
  --name <name> npm package name for the generated site.
147
151
  --site-title <title> Human-readable site title. Defaults to a title derived from --name.
148
152
  --template <name> Template name or package. Defaults to ${DEFAULT_TEMPLATE_NAME}.
149
- --template-url <url> Download a template tarball from a custom URL.
153
+ --template-url <url> Download a template zip artifact from a custom URL.
150
154
  --template-dir <dir> Copy a template from a local directory.
151
155
  --json Print command output as JSON.
152
156
  --help, -h Show this help.
153
157
 
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.`;
158
+ By default, slug init downloads the default template zip artifact. Set SLUGKIT_TEMPLATE_URL or pass --template-url to use a different zip artifact. Generated sites do not depend on the template artifact at runtime.`;
155
159
  const SITE_INIT_NEXT_STEPS = [
156
160
  "Install dependencies",
157
161
  "Copy and edit .env",
@@ -331,8 +335,18 @@ async function copyTemplateSite(source, destination, relativeDirectory = "") {
331
335
  }
332
336
  }
333
337
  }
334
- function shouldExcludeTemplateEntry(relativePath, _name) {
335
- return !isTemplateEntryIncluded(relativePath);
338
+ function shouldExcludeTemplateEntry(relativePath, name) {
339
+ const parts = relativePath.split(/[\\/]+/u).filter(Boolean);
340
+ return (parts.includes("node_modules") ||
341
+ parts.includes("dist") ||
342
+ parts.includes("build") ||
343
+ parts.includes("data") ||
344
+ parts.includes("coverage") ||
345
+ relativePath === "src/__tests__/dev-environment.test.ts" ||
346
+ relativePath === "src/federation/__tests__/follow.test.ts" ||
347
+ relativePath === ".env" ||
348
+ name.endsWith(".log") ||
349
+ name.endsWith(".tsbuildinfo"));
336
350
  }
337
351
  async function replaceGeneratedSitePlaceholders(directory, options) {
338
352
  await updateJsonFile(join(directory, "package.json"), (value) => ({
@@ -394,20 +408,20 @@ async function writeGeneratedSiteMarker(directory, options) {
394
408
  slugkitSite: true,
395
409
  name: options.name,
396
410
  siteTitle: options.siteTitle,
397
- template: `${SLUGKIT_TEMPLATE_PACKAGE_NAME}/${DEFAULT_TEMPLATE_NAME}`,
411
+ template: DEFAULT_TEMPLATE_NAME,
398
412
  generatedAt: new Date().toISOString(),
399
- packageMetadata: createGeneratedSitePackageMetadata(DEFAULT_TEMPLATE_METADATA),
413
+ packageMetadata: createGeneratedSitePackageMetadata(),
400
414
  };
401
415
  await writeFile(join(directory, ".slugkit-site.json"), `${JSON.stringify(marker, null, 2)}\n`, "utf8");
402
416
  }
403
- function createGeneratedSitePackageMetadata(metadata) {
417
+ function createGeneratedSitePackageMetadata() {
404
418
  return {
405
419
  template: {
406
- packageName: metadata.packageName,
407
- name: metadata.name,
408
- version: metadata.version,
420
+ packageName: "zip-artifact",
421
+ name: DEFAULT_TEMPLATE_NAME,
422
+ version: "downloaded",
409
423
  },
410
- packages: { ...metadata.includedSlugkitPackages },
424
+ packages: { ...GENERATED_SITE_SLUGKIT_DEPENDENCIES },
411
425
  advisoryOnly: true,
412
426
  migrationNotesUrl: "https://slugkit.com/docs/package-updates",
413
427
  };
@@ -421,20 +435,20 @@ async function resolveInitTemplateSource(context, options) {
421
435
  if (options.templateUrl !== undefined) {
422
436
  return downloadTemplateSite(context, options.templateUrl);
423
437
  }
424
- if (options.template !== undefined) {
425
- return resolveNamedTemplateSource(options.template);
438
+ if (options.template !== undefined && options.template !== DEFAULT_TEMPLATE_NAME) {
439
+ throw createInvalidUsageError(`Unsupported template: ${options.template}. Use --template-url for external template zip artifacts.`);
426
440
  }
427
- const localTemplateDirectory = findTemplateSiteDirectory(context.templateSiteDirectory);
428
- if (localTemplateDirectory !== undefined) {
429
- return { directory: localTemplateDirectory };
430
- }
431
- return { directory: getTemplateRoot() };
441
+ return downloadTemplateSite(context, getDefaultTemplateUrl(context));
432
442
  }
433
- function resolveNamedTemplateSource(template) {
434
- if (template === DEFAULT_TEMPLATE_NAME || template === SLUGKIT_TEMPLATE_PACKAGE_NAME) {
435
- return { directory: getTemplateRoot() };
443
+ function getDefaultTemplateUrl(context) {
444
+ if (context.templateAssetBaseUrl !== undefined) {
445
+ return `${context.templateAssetBaseUrl.replace(/\/+$/u, "")}/slugkit-site-template.zip`;
446
+ }
447
+ const environmentUrl = process.env.SLUGKIT_TEMPLATE_URL?.trim();
448
+ if (environmentUrl !== undefined && environmentUrl !== "") {
449
+ return environmentUrl;
436
450
  }
437
- throw createInvalidUsageError(`Unsupported template: ${template}. This CLI currently includes ${DEFAULT_TEMPLATE_NAME}; future releases may resolve external template packages.`);
451
+ return DEFAULT_TEMPLATE_URL;
438
452
  }
439
453
  async function downloadTemplateSite(context, templateUrl) {
440
454
  let parsedUrl;
@@ -453,17 +467,42 @@ async function downloadTemplateSite(context, templateUrl) {
453
467
  throw new CliError(`Failed to download Slugkit template from ${parsedUrl.toString()}: HTTP ${response.status}`, ExitCode.NetworkError);
454
468
  }
455
469
  const tempDirectory = await mkdtemp(join(tmpdir(), "slug-template-"));
456
- const archivePath = join(tempDirectory, "template.tgz");
470
+ const archivePath = join(tempDirectory, "template.zip");
457
471
  const extractDirectory = join(tempDirectory, "template");
458
472
  await mkdir(extractDirectory, { recursive: true });
459
473
  await writeFile(archivePath, Buffer.from(await response.arrayBuffer()));
460
- await extractTarball({ file: archivePath, cwd: extractDirectory });
461
- await assertTemplateSiteDirectory(extractDirectory);
474
+ new AdmZip(archivePath).extractAllTo(extractDirectory, true);
475
+ const templateDirectory = await findExtractedTemplateDirectory(extractDirectory);
476
+ await assertTemplateSiteDirectory(templateDirectory);
462
477
  return {
463
- directory: extractDirectory,
478
+ directory: templateDirectory,
464
479
  cleanup: () => rm(tempDirectory, { force: true, recursive: true }),
465
480
  };
466
481
  }
482
+ async function findExtractedTemplateDirectory(extractDirectory) {
483
+ if (await isTemplateSiteDirectory(extractDirectory)) {
484
+ return extractDirectory;
485
+ }
486
+ const entries = await readdir(extractDirectory, { withFileTypes: true });
487
+ for (const entry of entries) {
488
+ if (!entry.isDirectory())
489
+ continue;
490
+ const candidate = join(extractDirectory, entry.name);
491
+ if (await isTemplateSiteDirectory(candidate)) {
492
+ return candidate;
493
+ }
494
+ }
495
+ return extractDirectory;
496
+ }
497
+ async function isTemplateSiteDirectory(directory) {
498
+ try {
499
+ const packageJson = JSON.parse(await readFile(join(directory, "package.json"), "utf8"));
500
+ return packageJson.name === SITE_TEMPLATE_PACKAGE_NAME;
501
+ }
502
+ catch {
503
+ return false;
504
+ }
505
+ }
467
506
  async function assertTemplateSiteDirectory(directory) {
468
507
  try {
469
508
  const packageJson = JSON.parse(await readFile(join(directory, "package.json"), "utf8"));
@@ -478,32 +517,6 @@ async function assertTemplateSiteDirectory(directory) {
478
517
  throw createInvalidUsageError(`Template directory is not readable: ${directory}`);
479
518
  }
480
519
  }
481
- function findTemplateSiteDirectory(templateSiteDirectory) {
482
- if (templateSiteDirectory !== undefined) {
483
- if (templateSiteDirectory === null) {
484
- return undefined;
485
- }
486
- return templateSiteDirectory;
487
- }
488
- const commandsDirectory = dirname(fileURLToPath(import.meta.url));
489
- const candidates = [
490
- resolve(commandsDirectory, "..", "..", "template", "site"),
491
- resolve(process.cwd(), "template", "site"),
492
- resolve(process.cwd(), "..", "template", "site"),
493
- ];
494
- for (const candidate of candidates) {
495
- try {
496
- const packageJson = JSON.parse(readFileSync(join(candidate, "package.json"), "utf8"));
497
- if (packageJson.name === SITE_TEMPLATE_PACKAGE_NAME) {
498
- return candidate;
499
- }
500
- }
501
- catch {
502
- // Try the next candidate.
503
- }
504
- }
505
- return undefined;
506
- }
507
520
  function isNodeErrorWithCode(error, code) {
508
521
  return isRecord(error) && error.code === code;
509
522
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evcraddock/slug-cli",
3
- "version": "0.6.3",
3
+ "version": "0.6.4",
4
4
  "description": "Command-line tool for Slugkit sites.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -27,7 +27,6 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@evcraddock/slug-core": "0.1.1",
30
- "@evcraddock/slug-template": "0.1.3",
31
- "tar": "^7.5.16"
30
+ "adm-zip": "^0.5.18"
32
31
  }
33
32
  }