create-fff
Version:
Create FFF-related configuration files.
399 lines (386 loc) • 9.65 kB
JavaScript
var __defProp = Object.defineProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
// src/index.ts
import {
cancel as cancel3,
intro,
isCancel as isCancel3,
outro as outro2,
select
} from "@clack/prompts";
import minimist from "minimist";
import color2 from "picocolors";
// src/netlify-cms.ts
import { access, constants, mkdir, writeFile } from "node:fs/promises";
import { parse } from "node:path";
import { cancel as cancel2, confirm as confirm2, isCancel as isCancel2, note as note2, outro, text as text2 } from "@clack/prompts";
import color from "picocolors";
import { stringify } from "yaml";
// package.json
var version = "1.0.0";
// src/lib/netlify-cms/config.ts
import { exec } from "node:child_process";
import { promisify } from "node:util";
// src/lib/netlify-cms/collections.ts
var collections_exports = {};
__export(collections_exports, {
article: () => article,
note: () => note,
photo: () => photo
});
// src/lib/netlify-cms/fields.ts
var type = (type2) => ({
default: type2,
label: "Post Type",
name: "type",
widget: "hidden"
});
var common = [
{
label: "Created on",
name: "created",
widget: "datetime"
},
{
label: "Updated on",
name: "updated",
widget: "datetime"
},
{
label: "Published on",
name: "published",
widget: "datetime"
},
{
label: "Tags",
name: "tags",
widget: "list"
},
{
label: "Flags",
name: "flags",
widget: "list"
}
];
var title = {
label: "Title",
name: "title",
widget: "string"
};
var summary = {
label: "Summary",
name: "summary",
widget: "text"
};
var image = ({
featured,
object_media
}) => ({
label: featured ? "Featured Image" : "Photo",
name: "image",
...object_media ? {
fields: [
{
allow_multiple: false,
label: "Source",
name: "src",
widget: "image"
},
{
label: "Alternate Text",
name: "alt",
widget: "string"
},
{
label: "Figure Caption",
name: "figcaption",
widget: "string"
}
],
max: 1,
widget: "list"
} : { allow_multiple: false, widget: "image" }
});
var content = {
label: "Content",
name: "body",
widget: "markdown"
};
// src/lib/netlify-cms/collections.ts
var common2 = (options) => ({
create: true,
extension: "md",
// TODO: media_folder
// TODO: public_folder
filter: options.filter ? {
field: "type",
value: options.type
} : void 0,
folder: options.folder,
format: "yaml-frontmatter",
path: options.path.replace("{{type}}", options.type),
slug: options.slug.replace("{{type}}", options.type)
});
var article = (options) => ({
label: "Articles",
label_singular: "Article",
name: "article",
...common2({ ...options, type: "article" }),
fields: [
...common,
type("article"),
title,
summary,
image({ featured: true, object_media: options.object_media }),
content
]
});
var note = (options) => ({
label: "Notes",
label_singular: "Note",
name: "note",
...common2({ ...options, type: "note" }),
fields: [
...common,
type("note"),
summary,
content
]
});
var photo = (options) => ({
label: "Photos",
label_singular: "Photo",
name: "photo",
...common2({ ...options, type: "photo" }),
fields: [
...common,
type("photo"),
summary,
image({ featured: false, object_media: options.object_media }),
content
]
});
// src/lib/netlify-cms/config.ts
var config = async (options) => ({
backend: {
branch: await promisify(exec)("git branch --show-current").then((response) => response.stdout.trim()) ?? "main",
name: "git-gateway"
},
collections: options.collections.map(
(collection) => collections_exports[collection](options)
),
media_folder: options.media_folder,
public_folder: options.public_folder
});
// src/lib/netlify-cms/options.ts
import {
cancel,
confirm,
isCancel,
multiselect,
text
} from "@clack/prompts";
var fillOptions = async (options) => {
if (!options.collections) {
options.collections = await multiselect({
cursorAt: "note",
initialValues: ["article"],
message: "Choose the type of post you want:",
options: [
{ label: "Article", value: "article" },
{ label: "Note", value: "note" },
{ label: "Photo", value: "photo" }
],
required: true
});
}
if (isCancel(options.collections)) {
cancel("Operation cancelled");
return process.exit(0);
}
if (!options.object_media) {
options.object_media = await confirm({
initialValue: false,
message: "Do you want to use Object Media?"
});
}
if (isCancel(options.object_media)) {
cancel("Operation cancelled");
return process.exit(0);
}
if (!options.media_folder) {
options.media_folder = await text({
message: "media_folder",
placeholder: "public/uploads",
validate: (value) => !value && "Please enter a path."
});
}
if (isCancel(options.media_folder)) {
cancel("Operation cancelled");
return process.exit(0);
}
if (!options.public_folder) {
options.public_folder = await text({
message: "public_folder",
placeholder: "/uploads",
validate: (value) => !value && "Please enter a path."
});
}
if (isCancel(options.public_folder)) {
cancel("Operation cancelled");
return process.exit(0);
}
if (!options.folder) {
options.folder = await text({
message: "Where do you want to use as the posts root?",
placeholder: "src/posts",
validate: (value) => !value && "Please enter a path."
});
}
if (isCancel(options.folder)) {
cancel("Operation cancelled");
return process.exit(0);
}
if (!options.path) {
options.path = await text({
initialValue: "{{type}}/{{slug}}/index",
message: "path",
placeholder: "{{type}}/{{slug}}/index",
validate: (value) => !value && "Please enter a path."
});
}
if (isCancel(options.path)) {
cancel("Operation cancelled");
return process.exit(0);
}
if (!options.slug) {
options.slug = await text({
initialValue: "{{type}}/{{slug}}",
message: "slug",
placeholder: "{{type}}/{{slug}}",
validate: (value) => !value && "Please enter a path."
});
}
if (isCancel(options.slug)) {
cancel("Operation cancelled");
return process.exit(0);
}
return options;
};
// src/lib/netlify-cms/presets.ts
var presets_exports = {};
__export(presets_exports, {
urara: () => urara
});
var urara = {
argv: {
"config-path": "./urara/admin/config.yml"
},
options: {
filter: true,
folder: "urara",
media_folder: "urara/uploads",
path: "{{slug}}/+page",
public_folder: "/uploads",
slug: "{{slug}}"
}
};
// src/netlify-cms.ts
var netlifyCMS = async (argv) => {
argv = {
...argv,
...presets_exports[argv.preset]?.argv
};
const path = argv["config-path"] ?? await text2({
message: "Where should we create your Netlify CMS config?",
placeholder: "./public/admin/config.yml",
validate: (value) => {
if (!value)
return "Please enter a path.";
if (value[0] !== ".")
return "Please enter a relative path.";
}
});
if (isCancel2(path)) {
cancel2("Operation cancelled");
return process.exit(0);
}
await access(path, constants.F_OK).then(async () => {
const check = await confirm2({
message: color.yellow(
`${path} already has files. Do you want to continue?`
)
});
if (isCancel2(check) || check === false) {
cancel2("Operation cancelled");
return process.exit(0);
}
}).catch(console.error);
const options = await fillOptions(presets_exports[argv.preset]?.options);
if (options.path.includes("{{type}}")) {
options.filter = false;
} else {
const filter = await confirm2({
message: "Do you want to enable post type filtering?"
// '\nWhen not using the {{type}} folder,\nit will correctly categorize posts;\nhowever, it will cause posts that do not have a type value set to not be displayed.\nhttps://fff.js.org'
});
if (isCancel2(filter)) {
cancel2("Operation cancelled");
return process.exit(0);
}
options.filter = filter;
}
await mkdir(parse(path).dir, { recursive: true }).then(
async () => await writeFile(
path,
/** @see {@link https://github.com/decaporg/decap-cms/issues/1342} */
stringify(await config(options), {
aliasDuplicateObjects: false
})
)
).catch(console.error);
note2(`FFF Flavored Frontmatter
Version ${version}`);
outro("You're all set! ");
};
// src/index.ts
var main = async () => {
console.clear();
intro(color2.black(color2.bgCyan(" create-fff ")));
const argv = minimist(process.argv.slice(2));
const type2 = argv._[0] ?? await select({
message: "Choose what you need to create:",
options: [
{
hint: "config.yml",
label: "Netlify CMS Config (WIP)",
value: "netlify-cms"
},
{
hint: "contentlayer.config.ts",
label: "Contentlayer Schema (TODO)",
value: "contentlayer"
}
]
});
if (isCancel3(type2)) {
cancel3("Operation cancelled");
return process.exit(0);
}
switch (type2) {
case "netlify-cms":
case "decap-cms": {
await netlifyCMS(argv).catch(console.error);
process.exit(0);
}
default: {
outro2(type2);
process.exit(0);
}
}
};
main().catch(console.error);