@evcraddock/slug-cli 0.2.0 → 0.4.0
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/dist/commands.js +31 -23
- package/package.json +1 -1
package/dist/commands.js
CHANGED
|
@@ -19,8 +19,8 @@ Usage:
|
|
|
19
19
|
slug [--config <file>] init <directory> --name <name> [--site-title <title>] [--template-url <url>] [--template-dir <dir>] [--json]
|
|
20
20
|
slug [--config <file>] post list [--type article|link|note] [--status draft|published|all] [--tag <slug>] [--json]
|
|
21
21
|
slug [--config <file>] post show <slug> [--json]
|
|
22
|
-
slug [--config <file>] post create --type article|link|note --slug <slug> --content <text> [--title <text>] [--url <url>] [--excerpt <text>] [--tag <slug>]... [--source-id <id>] [--
|
|
23
|
-
slug [--config <file>] post edit <slug> [--slug <new-slug>] [--title <text>] [--content <text>] [--url <url>] [--excerpt <text>] [--tag <slug>]... [--source-id <id>] [--
|
|
22
|
+
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]
|
|
23
|
+
slug [--config <file>] post edit <slug> [--slug <new-slug>] [--title <text>] [--content <text>] [--url <url>] [--excerpt <text>] [--tag <slug>]... [--source-id <id>] [--credit-contact-id <id>]... [--json]
|
|
24
24
|
slug [--config <file>] post delete <slug> [--json]
|
|
25
25
|
slug [--config <file>] post publish <slug> [--json]
|
|
26
26
|
slug [--config <file>] post unpublish <slug> [--json]
|
|
@@ -298,17 +298,18 @@ async function assertDirectoryIsMissingOrEmpty(directory) {
|
|
|
298
298
|
throw createInvalidUsageError(`Cannot inspect target directory: ${directory}`);
|
|
299
299
|
}
|
|
300
300
|
}
|
|
301
|
-
async function copyTemplateSite(source, destination) {
|
|
301
|
+
async function copyTemplateSite(source, destination, relativeDirectory = "") {
|
|
302
302
|
const entries = await readdir(source, { withFileTypes: true });
|
|
303
303
|
for (const entry of entries) {
|
|
304
|
-
|
|
304
|
+
const relativePath = relativeDirectory === "" ? entry.name : join(relativeDirectory, entry.name);
|
|
305
|
+
if (shouldExcludeTemplateEntry(relativePath, entry.name)) {
|
|
305
306
|
continue;
|
|
306
307
|
}
|
|
307
308
|
const sourcePath = join(source, entry.name);
|
|
308
309
|
const destinationPath = join(destination, entry.name);
|
|
309
310
|
if (entry.isDirectory()) {
|
|
310
311
|
await mkdir(destinationPath, { recursive: true });
|
|
311
|
-
await copyTemplateSite(sourcePath, destinationPath);
|
|
312
|
+
await copyTemplateSite(sourcePath, destinationPath, relativePath);
|
|
312
313
|
continue;
|
|
313
314
|
}
|
|
314
315
|
if (entry.isFile()) {
|
|
@@ -317,8 +318,15 @@ async function copyTemplateSite(source, destination) {
|
|
|
317
318
|
}
|
|
318
319
|
}
|
|
319
320
|
}
|
|
320
|
-
|
|
321
|
-
|
|
321
|
+
const GENERATED_SITE_TEST_EXCLUDES = new Set([
|
|
322
|
+
join("src", "__tests__", "dev-environment.test.ts"),
|
|
323
|
+
join("src", "federation", "__tests__", "follow.test.ts"),
|
|
324
|
+
]);
|
|
325
|
+
function shouldExcludeTemplateEntry(relativePath, name) {
|
|
326
|
+
return (GENERATED_SITE_TEST_EXCLUDES.has(relativePath) ||
|
|
327
|
+
TEMPLATE_COPY_EXCLUDES.has(name) ||
|
|
328
|
+
name.endsWith(".log") ||
|
|
329
|
+
name.endsWith(".tsbuildinfo"));
|
|
322
330
|
}
|
|
323
331
|
async function replaceGeneratedSitePlaceholders(directory, options) {
|
|
324
332
|
await updateJsonFile(join(directory, "package.json"), (value) => ({
|
|
@@ -335,22 +343,20 @@ async function replaceGeneratedSitePlaceholders(directory, options) {
|
|
|
335
343
|
await replaceTextFile(join(directory, "README.md"), [
|
|
336
344
|
["# Slugkit Template Site", `# ${options.siteTitle}`],
|
|
337
345
|
[
|
|
338
|
-
"This is the in-repo Slugkit template website. It is used for local CLI/API
|
|
339
|
-
"This is a standalone Slugkit-compatible website generated from the Slugkit template. This site owns its code and can
|
|
346
|
+
"This is the in-repo Slugkit template website. It is toolkit source used for local CLI/API development and for `slug init`; it is not itself the deployable website you hand to a site owner. Generated sites should contain this website code without the `slug` CLI source code.\n\nInitialized sites are standalone Slugkit-compatible websites. After `slug init`, the generated site owns its code and may customize routes, templates, styles, assets, and deployment freely while preserving the Slugkit API contract that the `slug` CLI uses.",
|
|
347
|
+
"This is a standalone Slugkit-compatible website generated from the Slugkit template. This site owns its code and can customize routes, templates, styles, assets, and deployment freely while preserving the Slugkit API contract that the `slug` CLI uses.",
|
|
340
348
|
],
|
|
341
349
|
[
|
|
342
|
-
"
|
|
343
|
-
"
|
|
350
|
+
"Local toolkit development can run `make garage-setup` from the Slugkit repository root after `make dev` starts Garage. Standalone initialized sites should configure these values for their own local or production S3-compatible service before uploading media.",
|
|
351
|
+
"Configure these values for your own local or production S3-compatible service before uploading media.",
|
|
344
352
|
],
|
|
345
353
|
[
|
|
346
|
-
"
|
|
347
|
-
"
|
|
354
|
+
"## In-repository development\n\nWhen working inside the Slugkit toolkit repository, start the full local dev environment from the repository root:\n\n```bash\nmake dev\n```\n\nFrom `template/site`, you can also run the app and Tailwind watcher directly in separate terminals:\n\n```bash\nnpm run dev\nnpm run css:watch\n```\n\nRepository-root workspace commands are useful while developing the template itself:\n\n```bash\nnpm run db:migrate --workspace @slugkit/template-site\nnpm run db:status --workspace @slugkit/template-site\nnpm run test --workspace @slugkit/template-site\n```\n\nStandalone initialized sites should use the non-workspace commands shown in the quick start section.",
|
|
355
|
+
"## Local development\n\nInstall Overmind if it is not already available, then run the app and Tailwind watcher together from this site directory:\n\n```bash\nmake dev\n```\n\nThe generated `Procfile.dev` runs `npm run dev` for the Hono app and `npm run css:watch` for Tailwind. Use the non-workspace commands shown in the quick start section for migrations and local checks.",
|
|
348
356
|
],
|
|
349
|
-
["npm run db:migrate --workspace @slugkit/template-site", "npm run db:migrate"],
|
|
350
|
-
["npm run db:status --workspace @slugkit/template-site", "npm run db:status"],
|
|
351
357
|
[
|
|
352
|
-
"
|
|
353
|
-
"
|
|
358
|
+
"## Related specs\n\n- [Web Spec 08: Template Setup Documentation](../../docs/web-specs/08-template-setup-documentation.md)\n- [CLI Spec 12: Site Init Command](../../docs/cli-specs/12-site-init-command.md)\n- [CLI Specs](../../docs/cli-specs/README.md)\n- [API Route Specs](../../docs/specs/README.md)",
|
|
359
|
+
"## Related Slugkit references\n\n- Slugkit API documentation is available from a running site at `/api/v1/docs`.\n- The CLI compatibility check is `slug doctor`.\n- The generated site marker is `.slugkit-site.json`.",
|
|
354
360
|
],
|
|
355
361
|
]);
|
|
356
362
|
}
|
|
@@ -657,8 +663,9 @@ async function runLoginCommand(context, args) {
|
|
|
657
663
|
const config = await readConfig(context.configPath);
|
|
658
664
|
const apiBaseUrl = normalizeUrl(args[0] ?? config.apiBaseUrl ?? (await promptForValue(context, "API base URL: ")));
|
|
659
665
|
const authUrl = createBrowserAuthUrl(apiBaseUrl);
|
|
660
|
-
context.writer.stdout("Open this URL to create an API key:");
|
|
666
|
+
context.writer.stdout("Open this URL to log in and create an API key:");
|
|
661
667
|
context.writer.stdout(authUrl);
|
|
668
|
+
context.writer.stdout("If a browser does not open, copy the URL into your browser manually.");
|
|
662
669
|
if (context.openUrl !== undefined) {
|
|
663
670
|
try {
|
|
664
671
|
await context.openUrl(authUrl);
|
|
@@ -667,6 +674,7 @@ async function runLoginCommand(context, args) {
|
|
|
667
674
|
context.writer.stdout("Could not open a browser automatically.");
|
|
668
675
|
}
|
|
669
676
|
}
|
|
677
|
+
context.writer.stdout(`Paste the API key from the website. The CLI will verify it and save it to ${context.configPath}.`);
|
|
670
678
|
const apiKey = (await promptForValue(context, "API key: ")).trim();
|
|
671
679
|
if (apiKey === "") {
|
|
672
680
|
throw createInvalidUsageError("API key is required");
|
|
@@ -742,7 +750,7 @@ async function runPostsCreateCommand(context, args) {
|
|
|
742
750
|
"excerpt",
|
|
743
751
|
"tag",
|
|
744
752
|
"source-id",
|
|
745
|
-
"
|
|
753
|
+
"credit-contact-id",
|
|
746
754
|
]);
|
|
747
755
|
const body = createPostMutationInput(parsed.options, true);
|
|
748
756
|
const api = await createConfiguredApiContext(context);
|
|
@@ -768,7 +776,7 @@ async function runPostsEditCommand(context, args) {
|
|
|
768
776
|
"excerpt",
|
|
769
777
|
"tag",
|
|
770
778
|
"source-id",
|
|
771
|
-
"
|
|
779
|
+
"credit-contact-id",
|
|
772
780
|
]);
|
|
773
781
|
const body = createPostMutationInput(parsed.options, false);
|
|
774
782
|
if (Object.keys(body).length === 0) {
|
|
@@ -828,7 +836,7 @@ function parsePostFlags(args, allowed) {
|
|
|
828
836
|
if (value === undefined || value.startsWith("--")) {
|
|
829
837
|
throw createInvalidUsageError(`Option --${key} requires a value`);
|
|
830
838
|
}
|
|
831
|
-
if (key === "tag" || key === "
|
|
839
|
+
if (key === "tag" || key === "credit-contact-id") {
|
|
832
840
|
const current = options[key];
|
|
833
841
|
options[key] = [...(Array.isArray(current) ? current : []), value];
|
|
834
842
|
}
|
|
@@ -851,8 +859,8 @@ function createPostMutationInput(options, requireCreateFields) {
|
|
|
851
859
|
if (Array.isArray(options.tag)) {
|
|
852
860
|
input.tagSlugs = options.tag;
|
|
853
861
|
}
|
|
854
|
-
if (Array.isArray(options["
|
|
855
|
-
input.
|
|
862
|
+
if (Array.isArray(options["credit-contact-id"])) {
|
|
863
|
+
input.creditContactIds = options["credit-contact-id"].map((value) => readPositiveInteger(value, "credit-contact-id"));
|
|
856
864
|
}
|
|
857
865
|
if (requireCreateFields) {
|
|
858
866
|
for (const key of ["type", "slug", "content"]) {
|