@murumets-ee/create 0.39.0 → 0.40.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/cli.mjs +1 -1
- package/package.json +2 -2
- package/templates/template-demo.tar.gz +0 -0
package/dist/cli.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import*as e from"@clack/prompts";import t from"picocolors";import{existsSync as n,readFileSync as r}from"node:fs";import{lstat as i,mkdir as a,readFile as o,readdir as s,rename as c,writeFile as l}from"node:fs/promises";import{basename as u,dirname as d,join as f,resolve as p,sep as m}from"node:path";import{fileURLToPath as h}from"node:url";import{extract as g}from"tar";import{createHash as _}from"node:crypto";import{createServer as v}from"node:net";import{execFileSync as y}from"node:child_process";const b=/^[a-z0-9][a-z0-9-]{0,213}$/,x=new Set([`admin`,`front`]);function S(e){if(!e)return`Project name is required`;if(!b.test(e))return`Use lowercase letters, numbers, and hyphens only (must start with a letter or digit, max 214 chars).`;if(x.has(e))return`"${e}" is the name of a workspace member the scaffold emits (apps/admin, apps/front). Pick another name.`}function C(e,t){if(e===`demo`&&!t)return"Preset `demo` installs plugins via `lumi add` after deps resolve, so --install must be true (or pick `minimal`)."}function w(e){let t={},n=e.slice(2);for(let e=0;e<n.length;e++){let r=n[e];if(r!==void 0)if(r===`--full`)t.preset=`demo`;else if(r===`--preset`&&n[e+1]){let r=n[++e];(r===`minimal`||r===`demo`)&&(t.preset=r)}else if(r.startsWith(`--preset=`)){let e=r.slice(9);(e===`minimal`||e===`demo`)&&(t.preset=e)}else r===`--install`?t.install=!0:r===`--no-install`?t.install=!1:!r.startsWith(`-`)&&!t.name&&(t.name=r)}return t}async function T(t){let n=w(process.argv),r=n.name??t;if(r!==void 0){let t=S(r);if(t)return e.intro(`@murumets-ee/create`),e.cancel(t),null}if(n.preset!==void 0&&n.install!==void 0){let t=C(n.preset,n.install);if(t)return e.intro(`@murumets-ee/create`),e.cancel(t),null}if(r&&n.install!==void 0&&n.preset!==void 0)return{name:r,installDeps:n.install,preset:n.preset};e.intro(`@murumets-ee/create`);let i=r??await e.text({message:`Project name:`,placeholder:`my-project`,validate:S});if(e.isCancel(i))return e.cancel(`Cancelled.`),null;let a=n.preset??await e.select({message:`Preset:`,options:[{value:`demo`,label:`Demo`,hint:`auth + media + ticketing (recommended for first-time setup)`},{value:`minimal`,label:`Minimal`,hint:"auth + infrastructure only — add plugins with `lumi add`"}],initialValue:`demo`});if(e.isCancel(a))return e.cancel(`Cancelled.`),null;let o=n.install??await e.confirm({message:`Install dependencies?`,initialValue:!0});if(e.isCancel(o))return e.cancel(`Cancelled.`),null;let s=C(a,o);return s?(e.cancel(s),null):{name:i,installDeps:o,preset:a}}const E=5500;function D(e){return E+_(`sha1`).update(e).digest().readUInt32BE(0)%500}async function O(e){return new Promise(t=>{let n=v(),r=!1,i=e=>{r||(r=!0,n.close(),t(e))},a=setTimeout(()=>i(!1),500);n.once(`error`,()=>{clearTimeout(a),i(!1)}),n.once(`listening`,()=>{clearTimeout(a),i(!0)}),n.listen(e,`127.0.0.1`)})}async function k(e){let t=D(e);for(let e=0;e<500;e++){let n=E+(t-E+e)%500;if(await O(n))return n}return t}const A=process.platform===`win32`;function j(e,t,n){return y(e,t,{cwd:n?.cwd,stdio:`pipe`,encoding:`utf-8`,shell:A})}function M(e,t,n){y(e,t,{cwd:n?.cwd,stdio:`inherit`,shell:A})}const N=new Set([`.ts`,`.tsx`,`.mts`,`.json`,`.md`,`.yml`,`.yaml`,`.css`,`.html`]),P=new Set([`.env.example`,`Dockerfile`,`.gitignore`]),F=`__PROJECT_NAME__`,I=`__PROJECT_NAME_TITLE__`,L=`__POSTGRES_HOST_PORT__`;async function R(e,t){await z(e,t)}async function z(e,t){let n=S(e.name);if(n)throw Error(n);let r=C(e.preset,e.installDeps);if(r)throw Error(r);let i=f(process.cwd(),e.name),a=p(process.cwd()),o=p(i);if(o!==a&&!o.startsWith(a+m))throw Error(`Project directory ${o} resolves outside the current working directory ${a}. Refusing to write.`);t?.(`Extracting template...`),await H(i),await W(i),t?.(`Personalizing template...`);let s=await k(e.name);if(await K(i,e.name,s),await q(i,e.name),e.installDeps&&(t?.(`Installing dependencies...`),j(`pnpm`,[`install`],{cwd:i})),e.preset===`demo`){let e=f(i,...`apps/admin`.split(`/`)),n=[...new Set(B.flatMap(e=>V[e]))];t?.(`Installing demo-preset packages...`),M(`pnpm`,[`add`,...n],{cwd:e});for(let n of B)t?.(`Adding ${n} plugin...`),M(`pnpm`,[`exec`,`lumi`,`add`,n],{cwd:e})}}const B=[`taxonomy`,`blog`,`media`,`ticketing`,`commerce`],V={taxonomy:[`@murumets-ee/taxonomy`],blog:[`@murumets-ee/content`],media:[`@murumets-ee/media`],ticketing:[`@murumets-ee/ticketing-ui`,`@murumets-ee/ticketing`],commerce:[`@murumets-ee/commerce-ui`]};async function H(e){try{await a(e);return}catch(e){if(e.code!==`EEXIST`)throw e}await U(e)}async function U(e){if(!n(e))return;let t=await i(e);if(t.isSymbolicLink())throw Error(`${e} is a symbolic link. Refusing to scaffold through it — the template would be written to wherever it points, which is outside the directory you named. Remove the link, or pick a different project name.`);if(!t.isDirectory())throw Error(`${e} already exists and is not a directory. Refusing to scaffold over it — pick a different project name, or remove the file first.`);if((await s(e)).length>0)throw Error(`${e} already exists and is not empty. Refusing to scaffold over it — pick a different project name, or remove the directory first.`)}async function W(e){let t=G();if(!n(t))throw Error(`Template tarball missing: ${t}\nRun \`pnpm --filter @murumets-ee/create build:templates\` first.`);await g({file:t,cwd:e,filter:e=>{let t=e.startsWith(`/`)||/^[A-Za-z]:[/\\]/.test(e),n=e.split(/[/\\]/).includes(`..`);return t||n?(console.warn(`@murumets-ee/create: refusing tar entry with traversal: ${e}`),!1):!0}})}function G(){let e=d(h(import.meta.url)),t=e;for(let e=0;e<5;e++){let e=f(t,`package.json`);if(n(e))try{if(JSON.parse(r(e,`utf-8`)).name===`@murumets-ee/create`)return f(t,`templates`,`template-demo.tar.gz`)}catch(t){console.warn(`@murumets-ee/create: skipped malformed package.json at ${e}: ${t instanceof Error?t.message:String(t)}`)}let i=d(t);if(i===t)break;t=i}throw Error(`Could not find @murumets-ee/create package root above ${e}. template-demo.tar.gz cannot be located.`)}async function K(e,t,n){let r=Z(t),i=String(n);for await(let n of Y(e)){if(!X(n))continue;let e=await o(n,`utf-8`);!e.includes(F)&&!e.includes(I)&&!e.includes(L)||await l(n,e.split(I).join(r).split(F).join(t).split(L).join(i),`utf-8`)}}async function q(e,t){let n=[];async function r(e){let i=await s(e,{withFileTypes:!0});for(let a of i){if(!a.isDirectory()||J.has(a.name))continue;let i=f(e,a.name);await r(i),a.name.includes(F)&&n.push({from:i,to:f(e,a.name.split(F).join(t))})}}await r(e);for(let{from:e,to:t}of n)await c(e,t)}const J=new Set([`node_modules`,`dist`,`build`,`coverage`,`out`,`.git`,`.next`,`.turbo`]);async function*Y(e){let t=await s(e,{withFileTypes:!0});for(let n of t){let t=f(e,n.name);if(n.isDirectory()){if(J.has(n.name))continue;yield*Y(t)}else n.isFile()&&(yield t)}}function X(e){let t=u(e);if(P.has(t))return!0;let n=t.lastIndexOf(`.`);if(n<0)return!1;let r=t.slice(n);return N.has(r)}function Z(e){return e.split(/[-_\s]+/).filter(Boolean).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(` `)}async function Q(){let n=process.argv[2],r=await T(n);if(!r)return;let i=e.spinner();try{i.start(`Extracting template...`),await R(r,e=>{i.stop(`${t.green(`✓`)} Done`),i.start(e)}),i.stop(`${t.green(`✓`)} Done`);let n=[`cd ${r.name}`,`cp .env.example .env`,`# Edit .env — set DATABASE_URL and BETTER_AUTH_SECRET`,`cp .env apps/admin/.env`,`# The root .env is what docker compose reads; each app reads its own`,`# when you run it directly. Same file, two places.`,`docker compose up -d`,`pnpm db:migrate`,`pnpm --filter admin exec lumi create-admin`,`# Bootstraps the first admin user. Public sign-up is closed by default.`,`pnpm dev`,`# Sign in at /auth/sign-in with the admin credentials you just created
|
|
2
|
+
import*as e from"@clack/prompts";import t from"picocolors";import{existsSync as n,readFileSync as r}from"node:fs";import{lstat as i,mkdir as a,readFile as o,readdir as s,rename as c,writeFile as l}from"node:fs/promises";import{basename as u,dirname as d,join as f,resolve as p,sep as m}from"node:path";import{fileURLToPath as h}from"node:url";import{extract as g}from"tar";import{createHash as _}from"node:crypto";import{createServer as v}from"node:net";import{execFileSync as y}from"node:child_process";const b=/^[a-z0-9][a-z0-9-]{0,213}$/,x=new Set([`admin`,`front`]);function S(e){if(!e)return`Project name is required`;if(!b.test(e))return`Use lowercase letters, numbers, and hyphens only (must start with a letter or digit, max 214 chars).`;if(x.has(e))return`"${e}" is the name of a workspace member the scaffold emits (apps/admin, apps/front). Pick another name.`}function C(e,t){if(e===`demo`&&!t)return"Preset `demo` installs plugins via `lumi add` after deps resolve, so --install must be true (or pick `minimal`)."}function w(e){let t={},n=e.slice(2);for(let e=0;e<n.length;e++){let r=n[e];if(r!==void 0)if(r===`--full`)t.preset=`demo`;else if(r===`--preset`&&n[e+1]){let r=n[++e];(r===`minimal`||r===`demo`)&&(t.preset=r)}else if(r.startsWith(`--preset=`)){let e=r.slice(9);(e===`minimal`||e===`demo`)&&(t.preset=e)}else r===`--install`?t.install=!0:r===`--no-install`?t.install=!1:!r.startsWith(`-`)&&!t.name&&(t.name=r)}return t}async function T(t){let n=w(process.argv),r=n.name??t;if(r!==void 0){let t=S(r);if(t)return e.intro(`@murumets-ee/create`),e.cancel(t),null}if(n.preset!==void 0&&n.install!==void 0){let t=C(n.preset,n.install);if(t)return e.intro(`@murumets-ee/create`),e.cancel(t),null}if(r&&n.install!==void 0&&n.preset!==void 0)return{name:r,installDeps:n.install,preset:n.preset};e.intro(`@murumets-ee/create`);let i=r??await e.text({message:`Project name:`,placeholder:`my-project`,validate:S});if(e.isCancel(i))return e.cancel(`Cancelled.`),null;let a=n.preset??await e.select({message:`Preset:`,options:[{value:`demo`,label:`Demo`,hint:`auth + media + ticketing (recommended for first-time setup)`},{value:`minimal`,label:`Minimal`,hint:"auth + infrastructure only — add plugins with `lumi add`"}],initialValue:`demo`});if(e.isCancel(a))return e.cancel(`Cancelled.`),null;let o=n.install??await e.confirm({message:`Install dependencies?`,initialValue:!0});if(e.isCancel(o))return e.cancel(`Cancelled.`),null;let s=C(a,o);return s?(e.cancel(s),null):{name:i,installDeps:o,preset:a}}const E=5500;function D(e){return E+_(`sha1`).update(e).digest().readUInt32BE(0)%500}async function O(e){return new Promise(t=>{let n=v(),r=!1,i=e=>{r||(r=!0,n.close(),t(e))},a=setTimeout(()=>i(!1),500);n.once(`error`,()=>{clearTimeout(a),i(!1)}),n.once(`listening`,()=>{clearTimeout(a),i(!0)}),n.listen(e,`127.0.0.1`)})}async function k(e){let t=D(e);for(let e=0;e<500;e++){let n=E+(t-E+e)%500;if(await O(n))return n}return t}const A=process.platform===`win32`;function j(e,t,n){return y(e,t,{cwd:n?.cwd,stdio:`pipe`,encoding:`utf-8`,shell:A})}function M(e,t,n){y(e,t,{cwd:n?.cwd,stdio:`inherit`,shell:A})}const N=new Set([`.ts`,`.tsx`,`.mts`,`.json`,`.md`,`.yml`,`.yaml`,`.css`,`.html`]),P=new Set([`.env.example`,`Dockerfile`,`.gitignore`]),F=`__PROJECT_NAME__`,I=`__PROJECT_NAME_TITLE__`,L=`__POSTGRES_HOST_PORT__`;async function R(e,t){await z(e,t)}async function z(e,t){let n=S(e.name);if(n)throw Error(n);let r=C(e.preset,e.installDeps);if(r)throw Error(r);let i=f(process.cwd(),e.name),a=p(process.cwd()),o=p(i);if(o!==a&&!o.startsWith(a+m))throw Error(`Project directory ${o} resolves outside the current working directory ${a}. Refusing to write.`);t?.(`Extracting template...`),await H(i),await W(i),t?.(`Personalizing template...`);let s=await k(e.name);if(await K(i,e.name,s),await q(i,e.name),e.installDeps&&(t?.(`Installing dependencies...`),j(`pnpm`,[`install`],{cwd:i})),e.preset===`demo`){let e=f(i,...`apps/admin`.split(`/`)),n=[...new Set(B.flatMap(e=>V[e]))];t?.(`Installing demo-preset packages...`),M(`pnpm`,[`add`,...n],{cwd:e});for(let n of B)t?.(`Adding ${n} plugin...`),M(`pnpm`,[`exec`,`lumi`,`add`,n],{cwd:e})}}const B=[`taxonomy`,`blog`,`media`,`ticketing`,`commerce`],V={taxonomy:[`@murumets-ee/taxonomy`],blog:[`@murumets-ee/content`],media:[`@murumets-ee/media`],ticketing:[`@murumets-ee/ticketing-ui`,`@murumets-ee/ticketing`],commerce:[`@murumets-ee/commerce-ui`]};async function H(e){try{await a(e);return}catch(e){if(e.code!==`EEXIST`)throw e}await U(e)}async function U(e){if(!n(e))return;let t=await i(e);if(t.isSymbolicLink())throw Error(`${e} is a symbolic link. Refusing to scaffold through it — the template would be written to wherever it points, which is outside the directory you named. Remove the link, or pick a different project name.`);if(!t.isDirectory())throw Error(`${e} already exists and is not a directory. Refusing to scaffold over it — pick a different project name, or remove the file first.`);if((await s(e)).length>0)throw Error(`${e} already exists and is not empty. Refusing to scaffold over it — pick a different project name, or remove the directory first.`)}async function W(e){let t=G();if(!n(t))throw Error(`Template tarball missing: ${t}\nRun \`pnpm --filter @murumets-ee/create build:templates\` first.`);await g({file:t,cwd:e,filter:e=>{let t=e.startsWith(`/`)||/^[A-Za-z]:[/\\]/.test(e),n=e.split(/[/\\]/).includes(`..`);return t||n?(console.warn(`@murumets-ee/create: refusing tar entry with traversal: ${e}`),!1):!0}})}function G(){let e=d(h(import.meta.url)),t=e;for(let e=0;e<5;e++){let e=f(t,`package.json`);if(n(e))try{if(JSON.parse(r(e,`utf-8`)).name===`@murumets-ee/create`)return f(t,`templates`,`template-demo.tar.gz`)}catch(t){console.warn(`@murumets-ee/create: skipped malformed package.json at ${e}: ${t instanceof Error?t.message:String(t)}`)}let i=d(t);if(i===t)break;t=i}throw Error(`Could not find @murumets-ee/create package root above ${e}. template-demo.tar.gz cannot be located.`)}async function K(e,t,n){let r=Z(t),i=String(n);for await(let n of Y(e)){if(!X(n))continue;let e=await o(n,`utf-8`);!e.includes(F)&&!e.includes(I)&&!e.includes(L)||await l(n,e.split(I).join(r).split(F).join(t).split(L).join(i),`utf-8`)}}async function q(e,t){let n=[];async function r(e){let i=await s(e,{withFileTypes:!0});for(let a of i){if(!a.isDirectory()||J.has(a.name))continue;let i=f(e,a.name);await r(i),a.name.includes(F)&&n.push({from:i,to:f(e,a.name.split(F).join(t))})}}await r(e);for(let{from:e,to:t}of n)await c(e,t)}const J=new Set([`node_modules`,`dist`,`build`,`coverage`,`out`,`.git`,`.next`,`.turbo`]);async function*Y(e){let t=await s(e,{withFileTypes:!0});for(let n of t){let t=f(e,n.name);if(n.isDirectory()){if(J.has(n.name))continue;yield*Y(t)}else n.isFile()&&(yield t)}}function X(e){let t=u(e);if(P.has(t))return!0;let n=t.lastIndexOf(`.`);if(n<0)return!1;let r=t.slice(n);return N.has(r)}function Z(e){return e.split(/[-_\s]+/).filter(Boolean).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(` `)}async function Q(){let n=process.argv[2],r=await T(n);if(!r)return;let i=e.spinner();try{i.start(`Extracting template...`),await R(r,e=>{i.stop(`${t.green(`✓`)} Done`),i.start(e)}),i.stop(`${t.green(`✓`)} Done`);let n=[`cd ${r.name}`,`cp .env.example .env`,`# Edit .env — set DATABASE_URL and BETTER_AUTH_SECRET`,`cp .env apps/admin/.env`,`# The root .env is what docker compose reads; each app reads its own`,`# when you run it directly. Same file, two places.`,`docker compose up -d`,`pnpm db:migrate`,`pnpm --filter admin exec lumi create-admin`,`# Bootstraps the first admin user. Public sign-up is closed by default.`,`pnpm dev`,`# Sign in at /auth/sign-in with the admin credentials you just created`,`# Settings -> Site Settings -> Home Document picks what serves at / —`,`# unset, / 404s even though the page has its own working slug.`,``,`# The public Astro site is a second app:`,`cp .env apps/front/.env`,`# CONTENT_API_ORIGIN must carry the /api/content segment — the bare`,`# origin hits the CMS i18n middleware and the site 503s on every page.`,`pnpm dev:front`];e.note(n.join(`
|
|
3
3
|
`),`Next steps`),e.outro(`${t.green(`Success!`)} Your project is ready.`)}catch(n){i.stop(t.red(`Failed.`)),e.log.error(n instanceof Error?n.message:String(n)),process.exit(1)}}Q();export{};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@murumets-ee/create",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.40.0",
|
|
4
4
|
"license": "Elastic-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"tsdown": "^0.22.2",
|
|
31
31
|
"tsx": "^4.22.4",
|
|
32
32
|
"typescript": "^5.7.3",
|
|
33
|
-
"@murumets-ee/cli": "^0.
|
|
33
|
+
"@murumets-ee/cli": "^0.40.0"
|
|
34
34
|
},
|
|
35
35
|
"typeCoverage": {
|
|
36
36
|
"atLeast": 100
|
|
Binary file
|