create-pluto 0.5.0 → 0.6.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/README.md CHANGED
@@ -1,32 +1,31 @@
1
1
  # create-pluto
2
2
 
3
- A command-line tool to quickly set up a new Pluto project with your preferred configurations.
4
-
5
- ## Usage
3
+ Create a new [Pluto](https://pluto.vulmix.dev) project.
6
4
 
7
5
  ```bash
8
6
  npm create pluto@latest
9
-
10
- # or
11
- pnpm create pluto@latest
12
-
13
- # or
14
- bun create pluto@latest
15
7
  ```
16
8
 
17
- You can also pass a target directory and preselect a template shortcut.
9
+ The CLI asks for a folder, a template and a package manager. Then it installs the dependencies
10
+ and shows the command that starts the dev server.
11
+
12
+ ## Options
18
13
 
19
14
  ```bash
20
- npm create pluto@latest my-project -t supabase-shop
15
+ npm create pluto@latest my-blog -t core-blog --pm pnpm
21
16
  ```
22
17
 
23
- Supported template shortcuts currently include:
18
+ | Option | What it does |
19
+ |---|---|
20
+ | `<folder>` | The project folder. |
21
+ | `-t`, `--template` | `core-blog`, `supabase-blog` or `supabase-shop`. |
22
+ | `--pm`, `--package-manager` | `npm`, `pnpm`, `yarn`, `bun` or `deno`. |
23
+ | `--no-install` | Do not install the dependencies. |
24
24
 
25
- ```bash
26
- supabase-shop
27
- supabase-blog
28
- core-blog
29
- ```
25
+ `core-blog` needs no database: posts are Markdown files in GitHub.
26
+
27
+ **Docs:** [Create a project](https://pluto.vulmix.dev/getting-started/create-a-project)
28
+
29
+ ## License
30
30
 
31
- The `core-blog` template has no database. Posts are Markdown files in a GitHub repository. The
32
- core option works only with the blog template.
31
+ MIT
package/dist/index.mjs CHANGED
@@ -1,4 +1,3 @@
1
- import e from"node:fs";import t from"node:path";import{cancel as n,confirm as r,isCancel as i,log as a,password as o,select as s,spinner as c,text as l}from"@clack/prompts";import{downloadTemplate as u}from"giget";import{randomBytes as d}from"node:crypto";const f=/^[\w.-]+\/[\w.-]+$/,p=/\r?\n/,m=/^(["'])(.*)\1$/;function h(e){return f.test(e)}function g(e){return e.trim().split(p).join(`\\n`)}function _(e){return e.trim().replace(m,`$2`)}async function v(e,t,r={}){let a=await e({message:t,...r});return i(a)&&(n(`Operation cancelled.`),process.exit(0)),(a??``).toString().trim()}async function y(e={}){let t={repo:``,appSlug:``,appId:``,clientId:``,clientSecret:``,privateKeyPath:``};return e.interactive??process.stdin.isTTY?(a.info(`Posts are Markdown files in a GitHub repository. You can skip any question and fill the .env file later.`),t.repo=await v(l,`GitHub repository for the posts (owner/name, optional):`,{placeholder:`your-name/your-blog`,validate:e=>e&&!h(e.trim())?`Use the format owner/name, like your-name/your-blog.`:void 0}),t.appSlug=await v(l,`GitHub App slug (github.com/apps/<slug>, optional):`),t.appId=await v(l,`GitHub App ID (optional):`),t.clientId=await v(l,`GitHub App Client ID (optional):`),t.clientSecret=await v(o,`GitHub App Client secret (optional, hidden):`),t.privateKeyPath=_(await v(l,`Path to the GitHub App private key .pem file (optional):`)),t):t}function b(n,r=process.cwd()){if(!n)return``;try{let i=e.readFileSync(t.resolve(r,n),`utf-8`);return i.includes(`-----BEGIN`)?g(i):(a.warn(`The key file is not a PEM file. Skipping the private key.`),``)}catch{return a.warn(`Could not read the key file at ${n}. Skipping the private key.`),``}}function x(e,t=process.cwd()){return{envVars:{PLUTO_CMS_REPO:e.repo,PLUTO_CMS_APP_SLUG:e.appSlug,NUXT_PLUTO_CMS_SESSION_PASSWORD:d(32).toString(`base64url`),NUXT_PLUTO_CMS_GITHUB_APP_ID:e.appId,NUXT_PLUTO_CMS_GITHUB_CLIENT_ID:e.clientId,NUXT_PLUTO_CMS_GITHUB_CLIENT_SECRET:e.clientSecret,NUXT_PLUTO_CMS_GITHUB_PRIVATE_KEY:b(e.privateKeyPath,t)}}}function S(e){let t=[`Run "bun run dev" and open http://localhost:3000/admin. You are signed in as a local user, and the posts are files in the content/ folder. No GitHub App is needed for this.`,`Deploy the site (for example to Vercel). Then open /admin/login on the deployed site and select "Create the GitHub App". Follow the page: paste the values in the environment variables of the host, redeploy, and install the App on the repository of your blog.`,`Step by step: "GitHub CMS in 10 minutes" (https://pluto.vulmix.dev/github-cms/quickstart). The manual steps are in the template README:
2
- https://github.com/plutocms/pluto-core-blog-template#set-up-the-cms`];e.repo||t.splice(1,0,`Set PLUTO_CMS_REPO in .env, or deploy from a GitHub repository (Vercel gives the repository to Pluto).`),a.info(`Next steps:\n${t.map((e,t)=>`${t+1}. ${e}`).join(`
3
- `)}`)}const C=/^postgres(?:ql)?:\/\//i;function w(e){return URL.canParse(e)}function T(e){return C.test(e)}async function E(){let e,t,r;for(;!e;){let t=await l({message:`Type/Paste your Supabase URL:`});if(i(t)&&(n(`Operation cancelled.`),process.exit(0)),!t||!w(t)){a.error(`Enter a valid Supabase URL, like https://xxxxx.supabase.co.`);continue}e=t}for(;!t;){let e=await l({message:`Type/Paste your Supabase Key:`});if(i(e)&&(n(`Operation cancelled.`),process.exit(0)),!e){a.error(`Supabase Key is required.`);continue}t=e}for(a.info(`Now find your database connection string: open your Supabase project, go to Project Settings > Database > Connection string, and copy the URI (the pooler connection works too).`);!r;){let e=await l({message:`Type/Paste your Supabase database connection string:`});if(i(e)&&(n(`Operation cancelled.`),process.exit(0)),!e||!T(e)){a.error(`Enter a valid Postgres connection string, starting with postgres:// or postgresql://.`);continue}r=e}return{supabaseUrl:e,supabaseKey:t,databaseUrl:r}}function D(e,t,n){let r={};return r.SUPABASE_URL=e?.toString()??``,r.SUPABASE_KEY=t?.toString()??``,r.DATABASE_URL=n?.toString()??``,{envVars:r}}function O(t,n){let r=``;e.existsSync(t)?(r=e.readFileSync(t,`utf-8`),Object.entries(n).forEach(([e,t])=>{let n=RegExp(`^${e}=.*$`,`m`),i=`${e}="${t}"`;n.test(r)?r=r.replace(n,()=>i):r+=`\n${i}`})):(r=`# Pluto Configurations
4
- `,Object.entries(n).forEach(([e,t])=>{r+=`${e}="${t}"\n`})),e.writeFileSync(t,`${r.trim()}\n`)}const k={databases:[{value:`supabase`,label:`Supabase`,hint:`Hosted Postgres Database`,templates:[`shop`,`blog`]},{value:`core`,label:`None (Markdown files in GitHub)`,hint:`No database. Posts are Markdown files in a GitHub repository`,templates:[`blog`]}],templates:[{value:`shop`,label:`Shop (e-commerce)`,hint:`A full-featured online store template`},{value:`blog`,label:`Blog`,hint:`A simple blog template with a rich text editor`}]};function A(e){let t=k.databases.find(t=>t.value===e);return k.templates.filter(e=>t?.templates.includes(e.value))}function j(){return k.databases.flatMap(e=>A(e.value).map(t=>`${e.value}-${t.value}`))}function M(e){let t=(e??``).split(`-`),[n,r]=t,i=`Available options: ${j().join(`, `)}`,a=k.databases.find(e=>e.value===n),o=k.templates.some(e=>e.value===r);return t.length!==2||!a||!o?{ok:!1,message:`Unknown template "${e}". ${i}`}:a.templates.includes(r)?{ok:!0,databaseProvider:n,templateChoice:r}:{ok:!1,message:`The "${n}" option has no "${r}" template. ${i}`}}const N=process.cwd(),P={blue:e=>`\x1B[34m${e}\x1B[0m`,green:e=>`\x1B[32m${e}\x1B[0m`};function F(e){let t={};for(let n=0;n<e.length;n+=1){let r=e[n];if(r===`-t`||r===`--template`){t.templateShortcut=e[n+1],n+=1;continue}if(r?.startsWith(`--template=`)){t.templateShortcut=r.split(`=`)[1];continue}!r?.startsWith(`-`)&&!t.projectDir&&(t.projectDir=r)}return t}const I=F(process.argv.slice(2));let L;if(I.templateShortcut){let e=M(I.templateShortcut);e.ok||(a.error(e.message),process.exit(1)),L=e}const R=I.projectDir?I.projectDir:await l({message:`Where do you want to create your Pluto project?`,placeholder:`./my-pluto-project`,defaultValue:`my-pluto-project`});i(R)&&(n(`Operation cancelled.`),process.exit(0));const z=t.resolve(N,R);e.existsSync(z)||e.mkdirSync(z,{recursive:!0});const B=await l({message:`Website title:`,placeholder:`My Awesome Website`,defaultValue:`My Awesome Website`});i(B)&&(n(`Operation cancelled.`),process.exit(0));const V=L?L.databaseProvider:await s({message:`Pick a database provider.`,options:k.databases});i(V)&&(n(`Operation cancelled.`),process.exit(0));let H,U,W;if(V===`supabase`){a.warn(`You must have a Supabase project set up. If you do not have one, please create it at:\n${P.blue(`https://database.new/`)}.`);let{supabaseUrl:e,supabaseKey:t,databaseUrl:n}=await E();(!e||!t||!n)&&(a.error(`Supabase setup failed.`),process.exit(1)),H=e,U=t,W=n}const G=c();e.existsSync(`${N}/nuxt.config.ts`)&&(await r({message:`Nuxt project detected. Do you want to add Pluto environment variables?`})||process.exit(0));const K=A(String(V)),q=K.length===1?K[0]:void 0;q&&!L&&a.info(`Using the only template available: ${q.label}`);const J=L?L.templateChoice:q?.value??await s({message:`Select a template to use. You can add more layers as needed.`,options:K});i(J)&&(n(`Operation cancelled.`),process.exit(0)),e.readdirSync(z).length>0&&(await r({message:`The folder ${P.green(R)} is not empty. Do you want to continue and potentially overwrite existing files?`})||(n(`Operation cancelled.`),process.exit(0))),G.start(`Downloading Pluto template...`);const{source:Y}=await u(`gh:plutocms/pluto-${V}-${J}-template`,{dir:z,force:!0});a.info(`Template downloaded from ${P.green(Y)}`);const X=t.resolve(z,`.env`),Z={};let Q;if(V===`core`?(Z.NUXT_PUBLIC_WEBSITE_NAME=B?.toString()??``,G.stop(`Template downloaded.`),Q=await y(),Object.assign(Z,x(Q).envVars)):Z.WEBSITE_NAME=B?.toString()??``,V===`supabase`){let{envVars:e}=D(H,U,W);Object.assign(Z,e)}try{O(X,Z)}catch(e){a.error(`Failed to write to .env file: ${e}`),process.exit(1)}Q||G.stop(`All done!`),a.success(`Setup complete! 🎉`),a.info(`Nuxt project created in: ${P.green(R)}`),Q&&S(Q);export{};
1
+ import e from"node:fs";import t from"node:path";import{cancel as n,confirm as r,isCancel as i,log as a,note as o,outro as s,select as c,spinner as l,text as u}from"@clack/prompts";import{downloadTemplate as d}from"giget";import{installDependencies as f,installDependenciesCommand as p,runScriptCommand as m}from"nypm";import{randomBytes as h}from"node:crypto";function g(){return{envVars:{NUXT_PLUTO_CMS_SESSION_PASSWORD:h(32).toString(`base64url`)}}}const _=/^postgres(?:ql)?:\/\//i;function v(e){return URL.canParse(e)}function y(e){return _.test(e)}async function b(){let e,t,r;for(;!e;){let t=await u({message:`Type/Paste your Supabase URL:`});if(i(t)&&(n(`Operation cancelled.`),process.exit(0)),!t||!v(t)){a.error(`Enter a valid Supabase URL, like https://xxxxx.supabase.co.`);continue}e=t}for(;!t;){let e=await u({message:`Type/Paste your Supabase Key:`});if(i(e)&&(n(`Operation cancelled.`),process.exit(0)),!e){a.error(`Supabase Key is required.`);continue}t=e}for(a.info(`Now find your database connection string: open your Supabase project, go to Project Settings > Database > Connection string, and copy the URI (the pooler connection works too).`);!r;){let e=await u({message:`Type/Paste your Supabase database connection string:`});if(i(e)&&(n(`Operation cancelled.`),process.exit(0)),!e||!y(e)){a.error(`Enter a valid Postgres connection string, starting with postgres:// or postgresql://.`);continue}r=e}return{supabaseUrl:e,supabaseKey:t,databaseUrl:r}}function x(e,t,n){let r={};return r.SUPABASE_URL=e?.toString()??``,r.SUPABASE_KEY=t?.toString()??``,r.DATABASE_URL=n?.toString()??``,{envVars:r}}function S(t,n){let r=``;e.existsSync(t)?(r=e.readFileSync(t,`utf-8`),Object.entries(n).forEach(([e,t])=>{let n=RegExp(`^${e}=.*$`,`m`),i=`${e}="${t}"`;n.test(r)?r=r.replace(n,()=>i):r+=`\n${i}`})):(r=`# Pluto Configurations
2
+ `,Object.entries(n).forEach(([e,t])=>{r+=`${e}="${t}"\n`})),e.writeFileSync(t,`${r.trim()}\n`)}const C={databases:[{value:`supabase`,label:`Supabase`,hint:`Hosted Postgres Database`,templates:[`shop`,`blog`]},{value:`core`,label:`None (Markdown files in GitHub)`,hint:`No database. Posts are Markdown files in a GitHub repository`,templates:[`blog`]}],templates:[{value:`shop`,label:`Shop (e-commerce)`,hint:`A full-featured online store template`},{value:`blog`,label:`Blog`,hint:`A simple blog template with a rich text editor`}]};function w(e){let t=C.databases.find(t=>t.value===e);return C.templates.filter(e=>t?.templates.includes(e.value))}function T(){return C.databases.flatMap(e=>w(e.value).map(t=>`${e.value}-${t.value}`))}function E(e){let t=(e??``).split(`-`),[n,r]=t,i=`Available options: ${T().join(`, `)}`,a=C.databases.find(e=>e.value===n),o=C.templates.some(e=>e.value===r);return t.length!==2||!a||!o?{ok:!1,message:`Unknown template "${e}". ${i}`}:a.templates.includes(r)?{ok:!0,databaseProvider:n,templateChoice:r}:{ok:!1,message:`The "${n}" option has no "${r}" template. ${i}`}}const D=[`npm`,`pnpm`,`yarn`,`bun`,`deno`];function O(e){return typeof e==`string`&&D.includes(e)}function k(e){let t=e?.split(`/`)[0]?.trim();return O(t)?t:`npm`}function A(e){let n=[],r=t.relative(e.cwd,e.projectPath);return r&&n.push(`cd ${r.includes(` `)?`"${r}"`:r}`),e.installed||n.push(p(e.packageManager)),n.push(m(e.packageManager,`dev`)),n}const j=process.cwd(),M={blue:e=>`\x1B[34m${e}\x1B[0m`,green:e=>`\x1B[32m${e}\x1B[0m`,cyan:e=>`\x1B[36m${e}\x1B[0m`,dim:e=>`\x1B[2m${e}\x1B[0m`};function N(e){let t={};for(let n=0;n<e.length;n+=1){let r=e[n];if(r===`-t`||r===`--template`){t.templateShortcut=e[n+1],n+=1;continue}if(r?.startsWith(`--template=`)){t.templateShortcut=r.split(`=`)[1];continue}if(r===`--pm`||r===`--package-manager`){t.packageManager=e[n+1],n+=1;continue}if(r?.startsWith(`--pm=`)||r?.startsWith(`--package-manager=`)){t.packageManager=r.split(`=`)[1];continue}if(r===`--no-install`){t.install=!1;continue}!r?.startsWith(`-`)&&!t.projectDir&&(t.projectDir=r)}return t}const P=N(process.argv.slice(2));let F;if(P.templateShortcut){let e=E(P.templateShortcut);e.ok||(a.error(e.message),process.exit(1)),F=e}P.packageManager!==void 0&&!O(P.packageManager)&&(a.error(`Unknown package manager "${P.packageManager}". Use one of: ${D.join(`, `)}.`),process.exit(1));const I=P.projectDir?P.projectDir:await u({message:`Where do you want to create your Pluto project?`,placeholder:`./my-pluto-project`,defaultValue:`my-pluto-project`});i(I)&&(n(`Operation cancelled.`),process.exit(0));const L=t.resolve(j,I);e.existsSync(L)||e.mkdirSync(L,{recursive:!0});const R=`My Awesome Website`,z=process.stdin.isTTY?await u({message:`Website title:`,placeholder:R,defaultValue:R}):R;i(z)&&(n(`Operation cancelled.`),process.exit(0));const B=F?F.databaseProvider:await c({message:`Pick a database provider.`,options:C.databases});i(B)&&(n(`Operation cancelled.`),process.exit(0));let V,H,U;if(B===`supabase`){a.warn(`You must have a Supabase project set up. If you do not have one, please create it at:\n${M.blue(`https://database.new/`)}.`);let{supabaseUrl:e,supabaseKey:t,databaseUrl:n}=await b();(!e||!t||!n)&&(a.error(`Supabase setup failed.`),process.exit(1)),V=e,H=t,U=n}const W=l();e.existsSync(`${j}/nuxt.config.ts`)&&(await r({message:`Nuxt project detected. Do you want to add Pluto environment variables?`})||process.exit(0));const G=w(String(B)),K=G.length===1?G[0]:void 0;K&&!F&&a.info(`Using the only template available: ${K.label}`);const q=F?F.templateChoice:K?.value??await c({message:`Select a template to use. You can add more layers as needed.`,options:G});i(q)&&(n(`Operation cancelled.`),process.exit(0)),e.readdirSync(L).length>0&&(await r({message:`The folder ${M.green(I)} is not empty. Do you want to continue and potentially overwrite existing files?`})||(n(`Operation cancelled.`),process.exit(0))),W.start(`Downloading Pluto template...`);const{source:J}=await d(`gh:plutocms/pluto-${B}-${q}-template`,{dir:L,force:!0});W.stop(`Template downloaded from ${M.green(J)}`);const Y=t.resolve(L,`.env`),X={};if(B===`core`?(X.NUXT_PUBLIC_WEBSITE_NAME=z?.toString()??``,Object.assign(X,g().envVars)):X.WEBSITE_NAME=z?.toString()??``,B===`supabase`){let{envVars:e}=x(V,H,U);Object.assign(X,e)}try{S(Y,X)}catch(e){a.error(`Failed to write to .env file: ${e}`),process.exit(1)}const Z=k(process.env.npm_config_user_agent),Q=O(P.packageManager)?P.packageManager:process.stdin.isTTY?await c({message:`Which package manager do you want to use?`,options:D.map(e=>({value:e,label:e})),initialValue:Z}):Z;i(Q)&&(n(`Operation cancelled.`),process.exit(0));let $=!1;if(P.install!==!1){W.start(`Installing dependencies with ${Q}...`);try{await f({cwd:L,packageManager:Q,silent:!0}),$=!0,W.stop(`Dependencies installed.`)}catch{W.stop(`The install did not finish. Run it yourself, see below.`,1)}}o([...A({projectPath:L,cwd:j,packageManager:Q,installed:$}).map(e=>M.cyan(e)),``,M.dim(`Then open http://localhost:3000/admin`)].join(`
3
+ `),`Next steps`),s(`Docs: ${M.blue(`https://pluto.vulmix.dev/getting-started/create-a-project`)}`);export{};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-pluto",
3
3
  "type": "module",
4
- "version": "0.5.0",
4
+ "version": "0.6.0",
5
5
  "description": "CLI Wizard for creating a Pluto App",
6
6
  "author": "https://github.com/ojvribeiro",
7
7
  "license": "MIT",
@@ -44,7 +44,8 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@clack/prompts": "^0.11.0",
47
- "giget": "^3.3.1"
47
+ "giget": "^3.3.1",
48
+ "nypm": "^0.6.12"
48
49
  },
49
50
  "devDependencies": {
50
51
  "@antfu/eslint-config": "^7.7.3",