@murumets-ee/create 0.7.0 → 0.9.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 +2 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +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{readFile as i,readdir as a,rm as o,writeFile as s}from"node:fs/promises";import{dirname as c,join as l}from"node:path";import{fileURLToPath as u}from"node:url";import{extract as d}from"tar";import{
|
|
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)}}
|
|
2
|
+
import*as e from"@clack/prompts";import t from"picocolors";import{existsSync as n,readFileSync as r}from"node:fs";import{readFile as i,readdir as a,rm as o,writeFile as s}from"node:fs/promises";import{dirname as c,join as l}from"node:path";import{fileURLToPath as u}from"node:url";import{extract as d}from"tar";import{createHash as f}from"node:crypto";import{createServer as p}from"node:net";import{execSync as m}from"node:child_process";function h(e){let t={},n=e.slice(2);for(let e=0;e<n.length;e++){let r=n[e];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 g(t){let n=h(process.argv),r=n.name??t;if(n.preset===`demo`&&n.install===!1)return e.intro(`@murumets-ee/create`),e.cancel("Preset `demo` installs plugins via `lumi add` after deps resolve, so --install must be true (or pick `minimal`)."),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:e=>{if(!e)return`Project name is required`;if(!/^[a-z0-9-]+$/.test(e))return`Use lowercase letters, numbers, and hyphens only`}});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});return e.isCancel(o)?(e.cancel(`Cancelled.`),null):a===`demo`&&!o?(e.cancel("Preset `demo` installs plugins via `lumi add` after deps resolve, so --install must be true (or pick `minimal`)."),null):{name:i,installDeps:o,preset:a}}const _=5500,v=5999;function y(e){let t=f(`sha1`).update(e).digest();return v-_+1,_+t.readUInt32BE(0)%500}async function b(e){return new Promise(t=>{let n=p(),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 x(e){v-_+1;let t=y(e);for(let e=0;e<500;e++){let n=_+(t-_+e)%500;if(await b(n))return n}return t}async function S(e,t){for(let n of t)await o(l(e,n),{force:!0,recursive:!0})}function C(e,t){return t?.inherit?(m(e,{cwd:t.cwd,stdio:`inherit`}),``):m(e,{cwd:t?.cwd,stdio:`pipe`,encoding:`utf-8`})}const w=new Set([`.ts`,`.tsx`,`.mts`,`.json`,`.md`,`.yml`,`.yaml`,`.css`,`.html`]),T=new Set([`.env.example`,`Dockerfile`,`.gitignore`]),E=`__PROJECT_NAME__`,D=`__PROJECT_NAME_TITLE__`,O=`__POSTGRES_HOST_PORT__`;async function k(e,t){await A(e,t)}async function A(e,t){let n=l(process.cwd(),e.name);t?.(`Creating Next.js app...`),C(`pnpm create next-app@16 ${e.name} --typescript --tailwind --no-eslint --app --no-src-dir --turbopack --react-compiler --skip-install --import-alias "@/*"`),await S(n,[`app/page.tsx`,`app/page.module.css`,`app/fonts`,`app/globals.css`,`README.md`]),t?.(`Extracting template...`),await M(n),t?.(`Personalizing template...`);let r=await x(e.name);if(await P(n,e.name,r),e.installDeps&&(t?.(`Installing dependencies...`),C(`pnpm install`,{cwd:n})),e.preset===`demo`){if(!e.installDeps)throw Error("Preset `demo` requires --install because the `lumi add` step runs from the scaffolded project. Re-run without --no-install, or pass --preset=minimal.");for(let e of j)t?.(`Adding ${e} plugin...`),C(`pnpm exec lumi add ${e}`,{cwd:n,inherit:!0})}}const j=[`taxonomy`,`blog`,`media`,`ticketing`];async function M(e){let t=N();if(!n(t))throw Error(`Template tarball missing: ${t}\nRun \`pnpm --filter @murumets-ee/create build:templates\` first.`);await d({file:t,cwd:e})}function N(){let e=c(u(import.meta.url)),t=e;for(let e=0;e<5;e++){let e=l(t,`package.json`);if(n(e))try{if(JSON.parse(r(e,`utf-8`)).name===`@murumets-ee/create`)return l(t,`templates`,`template-demo.tar.gz`)}catch{}let i=c(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 P(e,t,n){let r=R(t),a=String(n);for await(let n of I(e)){if(!L(n))continue;let e=await i(n,`utf-8`);!e.includes(E)&&!e.includes(D)&&!e.includes(O)||await s(n,e.split(D).join(r).split(E).join(t).split(O).join(a),`utf-8`)}}const F=new Set([`node_modules`,`dist`,`build`,`coverage`,`out`]);async function*I(e){let t=await a(e,{withFileTypes:!0});for(let n of t){let t=l(e,n.name);if(n.isDirectory()){if(F.has(n.name)||n.name.startsWith(`.`))continue;yield*I(t)}else n.isFile()&&(yield t)}}function L(e){let t=e.lastIndexOf(`/`),n=t>=0?e.slice(t+1):e;if(T.has(n))return!0;let r=n.lastIndexOf(`.`);if(r<0)return!1;let i=n.slice(r);return w.has(i)}function R(e){return e.split(/[-_\s]+/).filter(Boolean).map(e=>e[0]?.toUpperCase()+e.slice(1)).join(` `)}async function z(){let n=process.argv[2],r=await g(n);if(!r)return;let i=e.spinner();try{i.start(`Creating Next.js app...`),await k(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`,`docker compose up -d`,`pnpm db:migrate`,`pnpm 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`];e.note(n.join(`
|
|
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)}}z();export{};
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/scaffold.ts"],"mappings":";;AAOA;;;;;AAEA;KAFY,MAAA;AAAA,UAEK,cAAA;EACf,IAAA;EACA,WAAA;EACA,MAAA,EAAQ,MAAA;AAAA;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/scaffold.ts"],"mappings":";;AAOA;;;;;AAEA;KAFY,MAAA;AAAA,UAEK,cAAA;EACf,IAAA;EACA,WAAA;EACA,MAAA,EAAQ,MAAA;AAAA;;;KCCE,gBAAA,IAAoB,OAAA;AAAA,iBAqBV,QAAA,CACpB,OAAA,EAAS,cAAA,EACT,UAAA,GAAa,gBAAA,GACZ,OAAA"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{existsSync as e,readFileSync as t}from"node:fs";import{readFile as n,readdir as r,rm as i,writeFile as a}from"node:fs/promises";import{dirname as o,join as s}from"node:path";import{fileURLToPath as c}from"node:url";import{extract as l}from"tar";import{
|
|
1
|
+
import{existsSync as e,readFileSync as t}from"node:fs";import{readFile as n,readdir as r,rm as i,writeFile as a}from"node:fs/promises";import{dirname as o,join as s}from"node:path";import{fileURLToPath as c}from"node:url";import{extract as l}from"tar";import{createHash as u}from"node:crypto";import{createServer as d}from"node:net";import{execSync as f}from"node:child_process";const p=5500,m=5999;function h(e){let t=u(`sha1`).update(e).digest();return m-p+1,p+t.readUInt32BE(0)%500}async function g(e){return new Promise(t=>{let n=d(),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 _(e){m-p+1;let t=h(e);for(let e=0;e<500;e++){let n=p+(t-p+e)%500;if(await g(n))return n}return t}async function v(e,t){for(let n of t)await i(s(e,n),{force:!0,recursive:!0})}function y(e,t){return t?.inherit?(f(e,{cwd:t.cwd,stdio:`inherit`}),``):f(e,{cwd:t?.cwd,stdio:`pipe`,encoding:`utf-8`})}const b=new Set([`.ts`,`.tsx`,`.mts`,`.json`,`.md`,`.yml`,`.yaml`,`.css`,`.html`]),x=new Set([`.env.example`,`Dockerfile`,`.gitignore`]),S=`__PROJECT_NAME__`,C=`__PROJECT_NAME_TITLE__`,w=`__POSTGRES_HOST_PORT__`;async function T(e,t){await E(e,t)}async function E(e,t){let n=s(process.cwd(),e.name);t?.(`Creating Next.js app...`),y(`pnpm create next-app@16 ${e.name} --typescript --tailwind --no-eslint --app --no-src-dir --turbopack --react-compiler --skip-install --import-alias "@/*"`),await v(n,[`app/page.tsx`,`app/page.module.css`,`app/fonts`,`app/globals.css`,`README.md`]),t?.(`Extracting template...`),await O(n),t?.(`Personalizing template...`);let r=await _(e.name);if(await A(n,e.name,r),e.installDeps&&(t?.(`Installing dependencies...`),y(`pnpm install`,{cwd:n})),e.preset===`demo`){if(!e.installDeps)throw Error("Preset `demo` requires --install because the `lumi add` step runs from the scaffolded project. Re-run without --no-install, or pass --preset=minimal.");for(let e of D)t?.(`Adding ${e} plugin...`),y(`pnpm exec lumi add ${e}`,{cwd:n,inherit:!0})}}const D=[`taxonomy`,`blog`,`media`,`ticketing`];async function O(t){let n=k();if(!e(n))throw Error(`Template tarball missing: ${n}\nRun \`pnpm --filter @murumets-ee/create build:templates\` first.`);await l({file:n,cwd:t})}function k(){let n=o(c(import.meta.url)),r=n;for(let n=0;n<5;n++){let n=s(r,`package.json`);if(e(n))try{if(JSON.parse(t(n,`utf-8`)).name===`@murumets-ee/create`)return s(r,`templates`,`template-demo.tar.gz`)}catch{}let i=o(r);if(i===r)break;r=i}throw Error(`Could not find @murumets-ee/create package root above ${n}. template-demo.tar.gz cannot be located.`)}async function A(e,t,r){let i=P(t),o=String(r);for await(let r of M(e)){if(!N(r))continue;let e=await n(r,`utf-8`);!e.includes(S)&&!e.includes(C)&&!e.includes(w)||await a(r,e.split(C).join(i).split(S).join(t).split(w).join(o),`utf-8`)}}const j=new Set([`node_modules`,`dist`,`build`,`coverage`,`out`]);async function*M(e){let t=await r(e,{withFileTypes:!0});for(let n of t){let t=s(e,n.name);if(n.isDirectory()){if(j.has(n.name)||n.name.startsWith(`.`))continue;yield*M(t)}else n.isFile()&&(yield t)}}function N(e){let t=e.lastIndexOf(`/`),n=t>=0?e.slice(t+1):e;if(x.has(n))return!0;let r=n.lastIndexOf(`.`);if(r<0)return!1;let i=n.slice(r);return b.has(i)}function P(e){return e.split(/[-_\s]+/).filter(Boolean).map(e=>e[0]?.toUpperCase()+e.slice(1)).join(` `)}export{T as scaffold};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["tarExtract"],"sources":["../src/utils.ts","../src/scaffold.ts"],"sourcesContent":["import { execSync } from 'node:child_process'\nimport { appendFile, writeFile as fsWriteFile, mkdir, readFile, rm } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\n\n/** Write a file, creating parent directories as needed */\nexport async function writeFile(filePath: string, content: string): Promise<void> {\n await mkdir(dirname(filePath), { recursive: true })\n await fsWriteFile(filePath, content, 'utf-8')\n}\n\n/** Read and parse package.json, merge in new deps/scripts, write back */\nexport async function mergePackageJson(\n pkgPath: string,\n merge: {\n type?: string\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n scripts?: Record<string, string>\n },\n): Promise<void> {\n const raw = await readFile(pkgPath, 'utf-8')\n const pkg = JSON.parse(raw)\n\n if (merge.type) {\n pkg.type = merge.type\n }\n if (merge.dependencies) {\n pkg.dependencies = { ...pkg.dependencies, ...merge.dependencies }\n }\n if (merge.devDependencies) {\n pkg.devDependencies = { ...pkg.devDependencies, ...merge.devDependencies }\n }\n if (merge.scripts) {\n pkg.scripts = { ...pkg.scripts, ...merge.scripts }\n }\n\n await fsWriteFile(pkgPath, `${JSON.stringify(pkg, null, 2)}\\n`, 'utf-8')\n}\n\n/** Delete files (silently ignores missing) */\nexport async function deleteFiles(basePath: string, files: string[]): Promise<void> {\n for (const file of files) {\n await rm(join(basePath, file), { force: true, recursive: true })\n }\n}\n\n/**\n * Run a shell command synchronously.\n *\n * `stdio: 'pipe'` by default so return-value-style callers get the\n * captured stdout. Pass `inherit: true` for commands whose output the\n * user should see in real time (and where the buffered error-on-fail\n * behavior of pipe would hide diagnostics — e.g. `pnpm exec lumi add`\n * in the demo-preset post-install loop).\n */\nexport function runCommand(\n cmd: string,\n options?: { cwd?: string; inherit?: boolean },\n): string {\n if (options?.inherit) {\n execSync(cmd, { cwd: options.cwd, stdio: 'inherit' })\n return ''\n }\n return execSync(cmd, {\n cwd: options?.cwd,\n stdio: 'pipe',\n encoding: 'utf-8',\n })\n}\n\n/** Append content to a file */\nexport async function appendToFile(filePath: string, content: string): Promise<void> {\n await appendFile(filePath, content, 'utf-8')\n}\n","import { existsSync, readFileSync } from 'node:fs'\nimport { readdir, readFile, writeFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { extract as tarExtract } from 'tar'\nimport type { ProjectOptions } from './types'\nimport { deleteFiles, runCommand } from './utils'\n\n// Major Next.js version passed to `pnpm create next-app@<ver>`.\n// Bumped when the toolkit as a whole moves to a new Next major.\nconst CREATE_NEXT_APP_VERSION = '16'\n\nexport type ProgressCallback = (message: string) => void\n\n// File extensions eligible for token replacement during scaffolding.\nconst TOKENIZABLE_EXTENSIONS = new Set<string>([\n '.ts',\n '.tsx',\n '.mts',\n '.json',\n '.md',\n '.yml',\n '.yaml',\n '.css',\n '.html',\n])\n\nconst TOKENIZABLE_BASENAMES = new Set<string>(['.env.example', 'Dockerfile', '.gitignore'])\n\nconst TOKEN_PROJECT_NAME = '__PROJECT_NAME__'\nconst TOKEN_PROJECT_NAME_TITLE = '__PROJECT_NAME_TITLE__'\n\nexport async function scaffold(\n options: ProjectOptions,\n onProgress?: ProgressCallback,\n): Promise<void> {\n await scaffoldSingle(options, onProgress)\n}\n\n// ---------------------------------------------------------------------------\n// scaffoldSingle — extract a prebuilt tarball, then tokenize.\n// ---------------------------------------------------------------------------\n\nasync function scaffoldSingle(\n options: ProjectOptions,\n onProgress?: ProgressCallback,\n): Promise<void> {\n const projectDir = join(process.cwd(), options.name)\n\n // 1. Run create-next-app to get an authoritative Next.js skeleton\n // (lockfile, ESLint config, default tsconfig, etc.)\n onProgress?.('Creating Next.js app...')\n runCommand(\n `pnpm create next-app@${CREATE_NEXT_APP_VERSION} ${options.name} ` +\n '--typescript --tailwind --no-eslint --app --no-src-dir --turbopack --react-compiler --skip-install --import-alias \"@/*\"',\n )\n\n // 2. Cleanup default Next.js artifacts that the template will replace\n await deleteFiles(projectDir, [\n 'app/page.tsx',\n 'app/page.module.css',\n 'app/fonts',\n 'app/globals.css',\n 'README.md',\n ])\n\n // 3. Extract the prebuilt template tarball over the project directory.\n // The template is the minimal Lumi skeleton — run `lumi add <plugin>`\n // after scaffolding to layer in plugins (auth is preinstalled).\n onProgress?.('Extracting template...')\n await extractTemplate(projectDir)\n\n // 4. Replace tokens in tokenizable files\n onProgress?.('Personalizing template...')\n await tokenizeProject(projectDir, options.name)\n\n // 5. Install deps\n if (options.installDeps) {\n onProgress?.('Installing dependencies...')\n runCommand('pnpm install', { cwd: projectDir })\n }\n\n // 6. Layer on demo-preset plugins via `lumi add`. Skipped for the minimal\n // preset — user runs `lumi add <plugin>` themselves. Requires the deps\n // step above, because `pnpm exec lumi` resolves the bin from\n // `node_modules/.bin/`.\n if (options.preset === 'demo') {\n if (!options.installDeps) {\n throw new Error(\n 'Preset `demo` requires --install because the `lumi add` step runs ' +\n 'from the scaffolded project. Re-run without --no-install, or pass ' +\n '--preset=minimal.',\n )\n }\n for (const plugin of DEMO_PRESET_PLUGINS) {\n onProgress?.(`Adding ${plugin} plugin...`)\n // stdio=inherit so the user sees `lumi add`'s own step-by-step\n // output + any error messages (otherwise they get buffered on\n // failure and surfaced as a generic execSync error).\n runCommand(`pnpm exec lumi add ${plugin}`, { cwd: projectDir, inherit: true })\n }\n }\n}\n\n/**\n * Optional plugins layered on top of the baseline skeleton for the demo\n * preset. Must stay in sync with `CATALOG` in `@murumets-ee/cli` minus\n * `auth` (which is preinstalled in the baseline `lumi.config.ts`). The\n * `preset-parity.test.ts` test reads `CATALOG` at test time and asserts\n * `[...DEMO_PRESET_PLUGINS, 'auth'].sort()` equals\n * `Object.keys(CATALOG).sort()` — rename or add an entry here whenever\n * the CLI catalog grows.\n */\n/**\n * Order matters. `taxonomy` scaffolds `entities/category.ts` +\n * `entities/tag.ts`; `blog` imports those indirectly via entity\n * references. Running `taxonomy` first keeps the scaffolded `Article`\n * reference-field target available at plugin-resolution time.\n *\n * `media` and `ticketing` don't depend on the entity files; they can\n * run in any position but live at the end for readability.\n */\nconst DEMO_PRESET_PLUGINS = ['taxonomy', 'blog', 'media', 'ticketing'] as const\n\n// ---------------------------------------------------------------------------\n// Tarball extraction\n// ---------------------------------------------------------------------------\n\nasync function extractTemplate(destDir: string): Promise<void> {\n const tarball = resolveTemplateTarball()\n if (!existsSync(tarball)) {\n throw new Error(\n `Template tarball missing: ${tarball}\\n` +\n `Run \\`pnpm --filter @murumets-ee/create build:templates\\` first.`,\n )\n }\n await tarExtract({ file: tarball, cwd: destDir })\n}\n\n/**\n * Locate the template tarball relative to the published `@murumets-ee/create`\n * package root.\n *\n * Anchors on the package's own `package.json` (matched by `name`) so a parent\n * project that happens to have a `templates/` directory at a higher path\n * can't hijack the resolution. This is a defense-in-depth measure against\n * template-substitution attacks where `create-lumi` is invoked from inside\n * a malicious project layout.\n */\nfunction resolveTemplateTarball(): string {\n const here = dirname(fileURLToPath(import.meta.url))\n let dir = here\n for (let i = 0; i < 5; i++) {\n const pkgJsonPath = join(dir, 'package.json')\n if (existsSync(pkgJsonPath)) {\n try {\n const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8')) as { name?: string }\n if (pkg.name === '@murumets-ee/create') {\n return join(dir, 'templates', 'template-demo.tar.gz')\n }\n } catch {\n // Malformed package.json — keep walking.\n }\n }\n const parent = dirname(dir)\n if (parent === dir) break // hit filesystem root\n dir = parent\n }\n throw new Error(\n `Could not find @murumets-ee/create package root above ${here}. ` +\n `template-demo.tar.gz cannot be located.`,\n )\n}\n\n\n// ---------------------------------------------------------------------------\n// Token replacement\n// ---------------------------------------------------------------------------\n\n/**\n * Walk the project directory and replace `__PROJECT_NAME__` /\n * `__PROJECT_NAME_TITLE__` tokens in eligible text files.\n *\n * Binary files (images, fonts) and `node_modules` / `.next` are skipped\n * to avoid corruption and pointless work.\n */\nasync function tokenizeProject(projectDir: string, projectName: string): Promise<void> {\n const titleName = toTitleCase(projectName)\n for await (const filePath of walkFiles(projectDir)) {\n if (!isTokenizable(filePath)) continue\n const original = await readFile(filePath, 'utf-8')\n if (!original.includes(TOKEN_PROJECT_NAME) && !original.includes(TOKEN_PROJECT_NAME_TITLE)) {\n continue\n }\n const replaced = original\n .split(TOKEN_PROJECT_NAME_TITLE)\n .join(titleName)\n .split(TOKEN_PROJECT_NAME)\n .join(projectName)\n await writeFile(filePath, replaced, 'utf-8')\n }\n}\n\n/**\n * Directories the tokenizer must skip.\n */\nconst SKIP_DIRS = new Set<string>([\n 'node_modules',\n 'dist',\n 'build',\n 'coverage',\n 'out',\n])\n\nasync function* walkFiles(root: string): AsyncGenerator<string> {\n const entries = await readdir(root, { withFileTypes: true })\n for (const entry of entries) {\n const full = join(root, entry.name)\n if (entry.isDirectory()) {\n if (SKIP_DIRS.has(entry.name) || entry.name.startsWith('.')) continue\n yield* walkFiles(full)\n } else if (entry.isFile()) {\n yield full\n }\n }\n}\n\nfunction isTokenizable(filePath: string): boolean {\n const slash = filePath.lastIndexOf('/')\n const basename = slash >= 0 ? filePath.slice(slash + 1) : filePath\n if (TOKENIZABLE_BASENAMES.has(basename)) return true\n const dot = basename.lastIndexOf('.')\n if (dot < 0) return false\n const ext = basename.slice(dot)\n return TOKENIZABLE_EXTENSIONS.has(ext)\n}\n\nfunction toTitleCase(name: string): string {\n return name\n .split(/[-_\\s]+/)\n .filter(Boolean)\n .map((part) => part[0]?.toUpperCase() + part.slice(1))\n .join(' ')\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["tarExtract"],"sources":["../src/port.ts","../src/utils.ts","../src/scaffold.ts"],"sourcesContent":["import { createHash } from 'node:crypto'\nimport { createServer } from 'node:net'\n\n/**\n * Pick a host port to map onto the scaffolded project's Postgres container.\n *\n * Why not 5432: every Lumi developer who has another Lumi project (or just a\n * local Postgres) on the standard port will hit a `port already in use`\n * collision the first time they run `docker compose up`. Surprising, slow to\n * diagnose, and easy to avoid.\n *\n * Strategy:\n * 1. Hash the project name into a stable base in `PORT_RANGE_LOW..HIGH` so\n * re-scaffolding the same name lands on the same port (predictable for\n * the developer, no churn in `.env`).\n * 2. Probe upward from that base by binding a temporary TCP server until\n * we find one that's actually free *right now*.\n * 3. If 500 consecutive probes fail (extremely unlikely), fall back to the\n * hashed base — the user will see docker's own error and can pick.\n */\n\nconst PORT_RANGE_LOW = 5500\nconst PORT_RANGE_HIGH = 5999\nconst PROBE_TIMEOUT_MS = 500\nconst MAX_PROBES = 500\n\n/**\n * Project-name → starting port. Stable + deterministic so the same project\n * always lands on the same base when no one else is squatting on it.\n */\nexport function hashProjectNameToPortBase(name: string): number {\n const digest = createHash('sha1').update(name).digest()\n const span = PORT_RANGE_HIGH - PORT_RANGE_LOW + 1\n return PORT_RANGE_LOW + (digest.readUInt32BE(0) % span)\n}\n\nasync function isPortFree(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const srv = createServer()\n let settled = false\n const finish = (free: boolean) => {\n if (settled) return\n settled = true\n srv.close()\n resolve(free)\n }\n const timer = setTimeout(() => finish(false), PROBE_TIMEOUT_MS)\n srv.once('error', () => {\n clearTimeout(timer)\n finish(false)\n })\n srv.once('listening', () => {\n clearTimeout(timer)\n finish(true)\n })\n // Bind on 127.0.0.1 — the docker-compose host port also binds to 127.0.0.1\n // (or 0.0.0.0, which conflicts with anything on the same port on lo).\n srv.listen(port, '127.0.0.1')\n })\n}\n\n/**\n * Find a free host port for the project. Walks upward from the hashed base\n * with wraparound inside `PORT_RANGE_LOW..HIGH`.\n */\nexport async function findFreePostgresPort(projectName: string): Promise<number> {\n const span = PORT_RANGE_HIGH - PORT_RANGE_LOW + 1\n const base = hashProjectNameToPortBase(projectName)\n for (let i = 0; i < MAX_PROBES; i++) {\n const port = PORT_RANGE_LOW + ((base - PORT_RANGE_LOW + i) % span)\n if (await isPortFree(port)) return port\n }\n return base\n}\n","import { execSync } from 'node:child_process'\nimport { appendFile, writeFile as fsWriteFile, mkdir, readFile, rm } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\n\n/** Write a file, creating parent directories as needed */\nexport async function writeFile(filePath: string, content: string): Promise<void> {\n await mkdir(dirname(filePath), { recursive: true })\n await fsWriteFile(filePath, content, 'utf-8')\n}\n\n/** Read and parse package.json, merge in new deps/scripts, write back */\nexport async function mergePackageJson(\n pkgPath: string,\n merge: {\n type?: string\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n scripts?: Record<string, string>\n },\n): Promise<void> {\n const raw = await readFile(pkgPath, 'utf-8')\n const pkg = JSON.parse(raw)\n\n if (merge.type) {\n pkg.type = merge.type\n }\n if (merge.dependencies) {\n pkg.dependencies = { ...pkg.dependencies, ...merge.dependencies }\n }\n if (merge.devDependencies) {\n pkg.devDependencies = { ...pkg.devDependencies, ...merge.devDependencies }\n }\n if (merge.scripts) {\n pkg.scripts = { ...pkg.scripts, ...merge.scripts }\n }\n\n await fsWriteFile(pkgPath, `${JSON.stringify(pkg, null, 2)}\\n`, 'utf-8')\n}\n\n/** Delete files (silently ignores missing) */\nexport async function deleteFiles(basePath: string, files: string[]): Promise<void> {\n for (const file of files) {\n await rm(join(basePath, file), { force: true, recursive: true })\n }\n}\n\n/**\n * Run a shell command synchronously.\n *\n * `stdio: 'pipe'` by default so return-value-style callers get the\n * captured stdout. Pass `inherit: true` for commands whose output the\n * user should see in real time (and where the buffered error-on-fail\n * behavior of pipe would hide diagnostics — e.g. `pnpm exec lumi add`\n * in the demo-preset post-install loop).\n */\nexport function runCommand(\n cmd: string,\n options?: { cwd?: string; inherit?: boolean },\n): string {\n if (options?.inherit) {\n execSync(cmd, { cwd: options.cwd, stdio: 'inherit' })\n return ''\n }\n return execSync(cmd, {\n cwd: options?.cwd,\n stdio: 'pipe',\n encoding: 'utf-8',\n })\n}\n\n/** Append content to a file */\nexport async function appendToFile(filePath: string, content: string): Promise<void> {\n await appendFile(filePath, content, 'utf-8')\n}\n","import { existsSync, readFileSync } from 'node:fs'\nimport { readdir, readFile, writeFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { extract as tarExtract } from 'tar'\nimport { findFreePostgresPort } from './port'\nimport type { ProjectOptions } from './types'\nimport { deleteFiles, runCommand } from './utils'\n\n// Major Next.js version passed to `pnpm create next-app@<ver>`.\n// Bumped when the toolkit as a whole moves to a new Next major.\nconst CREATE_NEXT_APP_VERSION = '16'\n\nexport type ProgressCallback = (message: string) => void\n\n// File extensions eligible for token replacement during scaffolding.\nconst TOKENIZABLE_EXTENSIONS = new Set<string>([\n '.ts',\n '.tsx',\n '.mts',\n '.json',\n '.md',\n '.yml',\n '.yaml',\n '.css',\n '.html',\n])\n\nconst TOKENIZABLE_BASENAMES = new Set<string>(['.env.example', 'Dockerfile', '.gitignore'])\n\nconst TOKEN_PROJECT_NAME = '__PROJECT_NAME__'\nconst TOKEN_PROJECT_NAME_TITLE = '__PROJECT_NAME_TITLE__'\nconst TOKEN_POSTGRES_HOST_PORT = '__POSTGRES_HOST_PORT__'\n\nexport async function scaffold(\n options: ProjectOptions,\n onProgress?: ProgressCallback,\n): Promise<void> {\n await scaffoldSingle(options, onProgress)\n}\n\n// ---------------------------------------------------------------------------\n// scaffoldSingle — extract a prebuilt tarball, then tokenize.\n// ---------------------------------------------------------------------------\n\nasync function scaffoldSingle(\n options: ProjectOptions,\n onProgress?: ProgressCallback,\n): Promise<void> {\n const projectDir = join(process.cwd(), options.name)\n\n // 1. Run create-next-app to get an authoritative Next.js skeleton\n // (lockfile, ESLint config, default tsconfig, etc.)\n onProgress?.('Creating Next.js app...')\n runCommand(\n `pnpm create next-app@${CREATE_NEXT_APP_VERSION} ${options.name} ` +\n '--typescript --tailwind --no-eslint --app --no-src-dir --turbopack --react-compiler --skip-install --import-alias \"@/*\"',\n )\n\n // 2. Cleanup default Next.js artifacts that the template will replace\n await deleteFiles(projectDir, [\n 'app/page.tsx',\n 'app/page.module.css',\n 'app/fonts',\n 'app/globals.css',\n 'README.md',\n ])\n\n // 3. Extract the prebuilt template tarball over the project directory.\n // The template is the minimal Lumi skeleton — run `lumi add <plugin>`\n // after scaffolding to layer in plugins (auth is preinstalled).\n onProgress?.('Extracting template...')\n await extractTemplate(projectDir)\n\n // 4. Replace tokens in tokenizable files. Pick a free Postgres host port\n // here (not 5432 — collides with anyone else running Postgres locally,\n // which is virtually every Lumi developer).\n onProgress?.('Personalizing template...')\n const postgresHostPort = await findFreePostgresPort(options.name)\n await tokenizeProject(projectDir, options.name, postgresHostPort)\n\n // 5. Install deps\n if (options.installDeps) {\n onProgress?.('Installing dependencies...')\n runCommand('pnpm install', { cwd: projectDir })\n }\n\n // 6. Layer on demo-preset plugins via `lumi add`. Skipped for the minimal\n // preset — user runs `lumi add <plugin>` themselves. Requires the deps\n // step above, because `pnpm exec lumi` resolves the bin from\n // `node_modules/.bin/`.\n if (options.preset === 'demo') {\n if (!options.installDeps) {\n throw new Error(\n 'Preset `demo` requires --install because the `lumi add` step runs ' +\n 'from the scaffolded project. Re-run without --no-install, or pass ' +\n '--preset=minimal.',\n )\n }\n for (const plugin of DEMO_PRESET_PLUGINS) {\n onProgress?.(`Adding ${plugin} plugin...`)\n // stdio=inherit so the user sees `lumi add`'s own step-by-step\n // output + any error messages (otherwise they get buffered on\n // failure and surfaced as a generic execSync error).\n runCommand(`pnpm exec lumi add ${plugin}`, { cwd: projectDir, inherit: true })\n }\n }\n}\n\n/**\n * Optional plugins layered on top of the baseline skeleton for the demo\n * preset. Must stay in sync with `CATALOG` in `@murumets-ee/cli` minus\n * `auth` (which is preinstalled in the baseline `lumi.config.ts`). The\n * `preset-parity.test.ts` test reads `CATALOG` at test time and asserts\n * `[...DEMO_PRESET_PLUGINS, 'auth'].sort()` equals\n * `Object.keys(CATALOG).sort()` — rename or add an entry here whenever\n * the CLI catalog grows.\n */\n/**\n * Order matters. `taxonomy` scaffolds `entities/category.ts` +\n * `entities/tag.ts`; `blog` imports those indirectly via entity\n * references. Running `taxonomy` first keeps the scaffolded `Article`\n * reference-field target available at plugin-resolution time.\n *\n * `media` and `ticketing` don't depend on the entity files; they can\n * run in any position but live at the end for readability.\n */\nconst DEMO_PRESET_PLUGINS = ['taxonomy', 'blog', 'media', 'ticketing'] as const\n\n// ---------------------------------------------------------------------------\n// Tarball extraction\n// ---------------------------------------------------------------------------\n\nasync function extractTemplate(destDir: string): Promise<void> {\n const tarball = resolveTemplateTarball()\n if (!existsSync(tarball)) {\n throw new Error(\n `Template tarball missing: ${tarball}\\n` +\n `Run \\`pnpm --filter @murumets-ee/create build:templates\\` first.`,\n )\n }\n await tarExtract({ file: tarball, cwd: destDir })\n}\n\n/**\n * Locate the template tarball relative to the published `@murumets-ee/create`\n * package root.\n *\n * Anchors on the package's own `package.json` (matched by `name`) so a parent\n * project that happens to have a `templates/` directory at a higher path\n * can't hijack the resolution. This is a defense-in-depth measure against\n * template-substitution attacks where `create-lumi` is invoked from inside\n * a malicious project layout.\n */\nfunction resolveTemplateTarball(): string {\n const here = dirname(fileURLToPath(import.meta.url))\n let dir = here\n for (let i = 0; i < 5; i++) {\n const pkgJsonPath = join(dir, 'package.json')\n if (existsSync(pkgJsonPath)) {\n try {\n const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8')) as { name?: string }\n if (pkg.name === '@murumets-ee/create') {\n return join(dir, 'templates', 'template-demo.tar.gz')\n }\n } catch {\n // Malformed package.json — keep walking.\n }\n }\n const parent = dirname(dir)\n if (parent === dir) break // hit filesystem root\n dir = parent\n }\n throw new Error(\n `Could not find @murumets-ee/create package root above ${here}. ` +\n `template-demo.tar.gz cannot be located.`,\n )\n}\n\n\n// ---------------------------------------------------------------------------\n// Token replacement\n// ---------------------------------------------------------------------------\n\n/**\n * Walk the project directory and replace `__PROJECT_NAME__` /\n * `__PROJECT_NAME_TITLE__` tokens in eligible text files.\n *\n * Binary files (images, fonts) and `node_modules` / `.next` are skipped\n * to avoid corruption and pointless work.\n */\nasync function tokenizeProject(\n projectDir: string,\n projectName: string,\n postgresHostPort: number,\n): Promise<void> {\n const titleName = toTitleCase(projectName)\n const portStr = String(postgresHostPort)\n for await (const filePath of walkFiles(projectDir)) {\n if (!isTokenizable(filePath)) continue\n const original = await readFile(filePath, 'utf-8')\n if (\n !original.includes(TOKEN_PROJECT_NAME) &&\n !original.includes(TOKEN_PROJECT_NAME_TITLE) &&\n !original.includes(TOKEN_POSTGRES_HOST_PORT)\n ) {\n continue\n }\n const replaced = original\n .split(TOKEN_PROJECT_NAME_TITLE)\n .join(titleName)\n .split(TOKEN_PROJECT_NAME)\n .join(projectName)\n .split(TOKEN_POSTGRES_HOST_PORT)\n .join(portStr)\n await writeFile(filePath, replaced, 'utf-8')\n }\n}\n\n/**\n * Directories the tokenizer must skip.\n */\nconst SKIP_DIRS = new Set<string>([\n 'node_modules',\n 'dist',\n 'build',\n 'coverage',\n 'out',\n])\n\nasync function* walkFiles(root: string): AsyncGenerator<string> {\n const entries = await readdir(root, { withFileTypes: true })\n for (const entry of entries) {\n const full = join(root, entry.name)\n if (entry.isDirectory()) {\n if (SKIP_DIRS.has(entry.name) || entry.name.startsWith('.')) continue\n yield* walkFiles(full)\n } else if (entry.isFile()) {\n yield full\n }\n }\n}\n\nfunction isTokenizable(filePath: string): boolean {\n const slash = filePath.lastIndexOf('/')\n const basename = slash >= 0 ? filePath.slice(slash + 1) : filePath\n if (TOKENIZABLE_BASENAMES.has(basename)) return true\n const dot = basename.lastIndexOf('.')\n if (dot < 0) return false\n const ext = basename.slice(dot)\n return TOKENIZABLE_EXTENSIONS.has(ext)\n}\n\nfunction toTitleCase(name: string): string {\n return name\n .split(/[-_\\s]+/)\n .filter(Boolean)\n .map((part) => part[0]?.toUpperCase() + part.slice(1))\n .join(' ')\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@murumets-ee/create",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"license": "Elastic-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"tsdown": "^0.21.7",
|
|
28
28
|
"tsx": "^4.21.0",
|
|
29
29
|
"typescript": "^5.7.3",
|
|
30
|
-
"@murumets-ee/cli": "^0.
|
|
30
|
+
"@murumets-ee/cli": "^0.9.0"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"build": "node ../../scripts/verify-catalog-sync.mjs && tsdown && tsx scripts/build-templates.ts",
|
|
Binary file
|