create-nextly-app 0.0.2-alpha.56 → 0.0.2-alpha.58

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.cjs CHANGED
@@ -6259,7 +6259,7 @@ var require_package = __commonJS({
6259
6259
  "package.json"(exports$1, module) {
6260
6260
  module.exports = {
6261
6261
  name: "create-nextly-app",
6262
- version: "0.0.2-alpha.56",
6262
+ version: "0.0.2-alpha.58",
6263
6263
  description: "CLI to scaffold Nextly in your Next.js project",
6264
6264
  license: "MIT",
6265
6265
  type: "module",
@@ -6316,7 +6316,7 @@ var require_package = __commonJS({
6316
6316
  vitest: "^4.1.0"
6317
6317
  },
6318
6318
  engines: {
6319
- node: ">=20.0.0"
6319
+ node: "^20.19.0 || ^22.12.0 || >=24.0.0"
6320
6320
  },
6321
6321
  keywords: [
6322
6322
  "nextly",
@@ -28670,6 +28670,47 @@ var import_fs_extra8 = __toESM(require_lib());
28670
28670
  var PROJECT_TYPES_WITH_FORM_BUILDER = /* @__PURE__ */ new Set([
28671
28671
  "blog"
28672
28672
  ]);
28673
+ var FONT_PACKAGE_RANGE = "^5.3.0";
28674
+ var FONT_PACKAGE_PATTERN = /@fontsource(?:-variable)?\/[a-z0-9-]+/g;
28675
+ async function collectFontDependencies(templateDirs) {
28676
+ const effective = await effectiveTemplateFiles(templateDirs);
28677
+ const found = /* @__PURE__ */ new Set();
28678
+ for (const file of effective.values()) {
28679
+ const contents = await import_fs_extra8.default.readFile(file, "utf8");
28680
+ for (const match of contents.matchAll(FONT_PACKAGE_PATTERN)) {
28681
+ found.add(match[0]);
28682
+ }
28683
+ }
28684
+ return [...found].sort();
28685
+ }
28686
+ async function effectiveTemplateFiles(templateDirs) {
28687
+ const effective = /* @__PURE__ */ new Map();
28688
+ for (const root of templateDirs) {
28689
+ if (!root || !await import_fs_extra8.default.pathExists(root)) continue;
28690
+ for (const file of await walkSourceFiles(root)) {
28691
+ effective.set(path15__default.default.relative(root, file), file);
28692
+ }
28693
+ }
28694
+ return effective;
28695
+ }
28696
+ var SEARCH_INDEX_SCRIPT = path15__default.default.join("scripts", "build-search-index.mjs");
28697
+ async function projectHasSearchIndexScript(targetDir) {
28698
+ return import_fs_extra8.default.pathExists(path15__default.default.join(targetDir, SEARCH_INDEX_SCRIPT));
28699
+ }
28700
+ async function walkSourceFiles(root) {
28701
+ const out = [];
28702
+ const entries = await import_fs_extra8.default.readdir(root, { withFileTypes: true });
28703
+ for (const entry of entries) {
28704
+ const full = path15__default.default.join(root, entry.name);
28705
+ if (entry.isDirectory()) {
28706
+ if (entry.name === "node_modules" || entry.name === ".next") continue;
28707
+ out.push(...await walkSourceFiles(full));
28708
+ } else if (/\.(?:tsx?|jsx?|mjs|cjs|css)$/.test(entry.name)) {
28709
+ out.push(full);
28710
+ }
28711
+ }
28712
+ return out;
28713
+ }
28673
28714
  function projectUsesFormBuilder(projectType) {
28674
28715
  return PROJECT_TYPES_WITH_FORM_BUILDER.has(projectType);
28675
28716
  }
@@ -28691,6 +28732,15 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
28691
28732
  ".cjs"
28692
28733
  ]);
28693
28734
  var SKIP_FILES = /* @__PURE__ */ new Set([".DS_Store", "Thumbs.db", ".gitkeep"]);
28735
+ var IGNORE_FILE_IN_TEMPLATE = "gitignore";
28736
+ var IGNORE_FILE_IN_PROJECT = ".gitignore";
28737
+ async function restoreIgnoreFile(targetDir) {
28738
+ const shipped = path15__default.default.join(targetDir, IGNORE_FILE_IN_TEMPLATE);
28739
+ if (!await import_fs_extra8.default.pathExists(shipped)) return;
28740
+ await import_fs_extra8.default.move(shipped, path15__default.default.join(targetDir, IGNORE_FILE_IN_PROJECT), {
28741
+ overwrite: true
28742
+ });
28743
+ }
28694
28744
  function resolveTemplatePath(localTemplatePath) {
28695
28745
  const __dirname = path15__default.default.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.cjs', document.baseURI).href))));
28696
28746
  const fromDist = path15__default.default.resolve(__dirname, "../templates");
@@ -28814,7 +28864,7 @@ async function resolveRuntimeVersions() {
28814
28864
  resolvedRuntimeVersions = Object.fromEntries(entries);
28815
28865
  return resolvedRuntimeVersions;
28816
28866
  }
28817
- async function generatePackageJson(projectName, database, useYalc = false, projectType = "blank") {
28867
+ async function generatePackageJson(projectName, database, useYalc = false, projectType = "blank", templateDirs = [], targetDir) {
28818
28868
  if (projectType === "plugin") {
28819
28869
  return generatePluginPackageJson(projectName, useYalc);
28820
28870
  }
@@ -28826,6 +28876,9 @@ async function generatePackageJson(projectName, database, useYalc = false, proje
28826
28876
  };
28827
28877
  dependencies["@tanstack/react-query"] = "^5.62.0";
28828
28878
  dependencies["lucide-react"] = "^0.544.0";
28879
+ for (const font of await collectFontDependencies(templateDirs)) {
28880
+ dependencies[font] = FONT_PACKAGE_RANGE;
28881
+ }
28829
28882
  if (!useYalc) {
28830
28883
  const versions = await resolveNextlyVersions(
28831
28884
  templateNextlyChannel(projectType)
@@ -28848,12 +28901,10 @@ async function generatePackageJson(projectName, database, useYalc = false, proje
28848
28901
  "@tailwindcss/postcss": PINNED_VERSIONS["@tailwindcss/postcss"],
28849
28902
  tailwindcss: PINNED_VERSIONS.tailwindcss,
28850
28903
  eslint: PINNED_VERSIONS.eslint,
28851
- "eslint-config-next": runtimeVersions["eslint-config-next"],
28852
- // Pagefind powers /search in the blog template. Zero-config
28853
- // static index generated at `next build` time. Templates that
28854
- // don't ship a /search page simply won't invoke it.
28855
- pagefind: "^1.1.0"
28904
+ "eslint-config-next": runtimeVersions["eslint-config-next"]
28856
28905
  };
28906
+ const shipsSearchIndex = targetDir ? await projectHasSearchIndexScript(targetDir) : false;
28907
+ if (shipsSearchIndex) devDependencies.pagefind = "^1.1.0";
28857
28908
  const pkg2 = {
28858
28909
  name: projectName,
28859
28910
  version: "0.1.0",
@@ -28865,11 +28916,25 @@ async function generatePackageJson(projectName, database, useYalc = false, proje
28865
28916
  // prompts, and child supervision. `nextly dev` is gone; the only
28866
28917
  // supported dev command is the standard `next dev`.
28867
28918
  dev: "next dev --turbopack",
28868
- // Build: migrate DB + compile Next.js + (if present) generate
28869
- // the Pagefind search index. Templates without the search
28870
- // script silently skip the last step.
28871
- build: "nextly migrate && next build && (test -f scripts/build-search-index.mjs && node scripts/build-search-index.mjs || true)",
28872
- "search:index": "node scripts/build-search-index.mjs",
28919
+ // Build: migrate the database, compile Next.js, and generate the Pagefind
28920
+ // search index for the templates that ship its builder.
28921
+ //
28922
+ // Whether that last step appears is decided HERE, from the files the
28923
+ // scaffold actually received, rather than by a shell conditional in the
28924
+ // script. Two reasons, and both were live:
28925
+ //
28926
+ // `npm run` uses cmd.exe on Windows, which has no `test` and no `true` —
28927
+ // so the `(test -f … || true)` form this replaces failed for every Windows
28928
+ // user, after `next build` had already succeeded. `&&` is all that remains,
28929
+ // and cmd.exe understands it.
28930
+ //
28931
+ // And `|| true` turned a failed index build into a successful one. The
28932
+ // search page then ships pointing at an index that was never written,
28933
+ // which fails in the browser rather than in CI.
28934
+ build: shipsSearchIndex ? "nextly migrate && next build && node scripts/build-search-index.mjs" : "nextly migrate && next build",
28935
+ // Offered only where the file exists. A script that always fails is worse
28936
+ // than an absent one: it reads as a supported command.
28937
+ ...shipsSearchIndex ? { "search:index": "node scripts/build-search-index.mjs" } : {},
28873
28938
  start: "next start",
28874
28939
  lint: "next lint",
28875
28940
  nextly: "nextly",
@@ -28879,8 +28944,11 @@ async function generatePackageJson(projectName, database, useYalc = false, proje
28879
28944
  "db:setup": "nextly db:sync",
28880
28945
  "db:migrate": "nextly migrate",
28881
28946
  "db:migrate:status": "nextly migrate:status",
28947
+ // No `db:migrate:reset`: `nextly migrate:reset` is not a command the CLI
28948
+ // registers, so the script it generated failed for every scaffolded
28949
+ // project. `migrate:fresh` is the one that drops all tables and re-runs
28950
+ // the migrations, and it is already exposed above.
28882
28951
  "db:migrate:fresh": "nextly migrate:fresh",
28883
- "db:migrate:reset": "nextly migrate:reset",
28884
28952
  "types:generate": "nextly generate:types"
28885
28953
  },
28886
28954
  dependencies,
@@ -28982,13 +29050,46 @@ function generatePnpmWorkspaceYaml() {
28982
29050
  # compiled binding (sqlite apps crash at boot) and sharp/esbuild degrade.
28983
29051
  #
28984
29052
  # pnpm 11+ reads \`allowBuilds\`; pnpm 10.6+ reads \`onlyBuiltDependencies\`.
28985
- # npm, yarn, and pnpm 9 ignore this file (they run build scripts by default).
29053
+ # npm and yarn ignore this file; pnpm 9 needs neither key, because it runs
29054
+ # build scripts by default.
29055
+ #
29056
+ # It does still READ the file, which is what the empty \`packages\` list is
29057
+ # for: through pnpm 9.3 the presence of this file declares a workspace, and
29058
+ # a missing \`packages\` key fails the install outright. This app has no
29059
+ # workspace members.
29060
+ packages: []
28986
29061
  allowBuilds:
28987
29062
  ${allowBuilds}
28988
29063
  onlyBuiltDependencies:
28989
29064
  ${onlyBuilt}
28990
29065
  `;
28991
29066
  }
29067
+ function generateNpmrc(packageManager) {
29068
+ if (packageManager !== "pnpm") return null;
29069
+ return `# This project is a single package, not a monorepo. pnpm 9 treats the
29070
+ # pnpm-workspace.yaml shipped for its build allowlist as declaring a
29071
+ # workspace root, which makes \`pnpm add <pkg>\` demand a -w flag. There is no
29072
+ # other package here for that check to protect.
29073
+ ${NPMRC_WORKSPACE_ROOT_KEY}=true
29074
+ `;
29075
+ }
29076
+ var NPMRC_WORKSPACE_ROOT_KEY = "ignore-workspace-root-check";
29077
+ async function writeScaffoldNpmrc(targetDir, packageManager) {
29078
+ const addition = generateNpmrc(packageManager);
29079
+ if (!addition) return;
29080
+ const npmrcPath = path15__default.default.join(targetDir, ".npmrc");
29081
+ const link = await import_fs_extra8.default.lstat(npmrcPath).catch(() => null);
29082
+ if (link?.isSymbolicLink()) return;
29083
+ if (link && link.nlink > 1) return;
29084
+ const existing = link ? await import_fs_extra8.default.readFile(npmrcPath, "utf-8") : "";
29085
+ const alreadyDeclared = new RegExp(
29086
+ `^\\s*${NPMRC_WORKSPACE_ROOT_KEY}\\s*=`,
29087
+ "m"
29088
+ ).test(existing);
29089
+ if (alreadyDeclared) return;
29090
+ const separator = existing === "" || existing.endsWith("\n") ? "" : "\n";
29091
+ await import_fs_extra8.default.writeFile(npmrcPath, existing + separator + addition, "utf-8");
29092
+ }
28992
29093
  async function copyTemplate(options) {
28993
29094
  const {
28994
29095
  projectName,
@@ -28999,6 +29100,7 @@ async function copyTemplate(options) {
28999
29100
  useYalc = false,
29000
29101
  approach,
29001
29102
  templateSource,
29103
+ packageManager,
29002
29104
  allowExistingTarget = false
29003
29105
  } = options;
29004
29106
  if (!allowExistingTarget && targetDir !== process.cwd() && await import_fs_extra8.default.pathExists(targetDir)) {
@@ -29017,7 +29119,13 @@ async function copyTemplate(options) {
29017
29119
  typeDir = path15__default.default.join(templatesRoot, projectType);
29018
29120
  }
29019
29121
  if (projectType === "plugin") {
29020
- await copyPluginTemplate({ projectName, typeDir, targetDir, useYalc });
29122
+ await copyPluginTemplate({
29123
+ projectName,
29124
+ typeDir,
29125
+ targetDir,
29126
+ useYalc,
29127
+ packageManager
29128
+ });
29021
29129
  return;
29022
29130
  }
29023
29131
  if (!await import_fs_extra8.default.pathExists(baseDir)) {
@@ -29076,6 +29184,13 @@ async function copyTemplate(options) {
29076
29184
  overwrite: false
29077
29185
  });
29078
29186
  }
29187
+ const templateScriptsDir = path15__default.default.join(typeDir, "scripts");
29188
+ if (await import_fs_extra8.default.pathExists(templateScriptsDir)) {
29189
+ await import_fs_extra8.default.copy(templateScriptsDir, path15__default.default.join(targetDir, "scripts"), {
29190
+ overwrite: false,
29191
+ filter: (src) => !SKIP_FILES.has(path15__default.default.basename(src))
29192
+ });
29193
+ }
29079
29194
  const frontendPagePath = path15__default.default.join(
29080
29195
  targetDir,
29081
29196
  "src",
@@ -29091,7 +29206,9 @@ async function copyTemplate(options) {
29091
29206
  projectName,
29092
29207
  database,
29093
29208
  useYalc,
29094
- projectType
29209
+ projectType,
29210
+ [baseDir, typeDir],
29211
+ targetDir
29095
29212
  );
29096
29213
  await import_fs_extra8.default.writeFile(
29097
29214
  path15__default.default.join(targetDir, "package.json"),
@@ -29103,6 +29220,8 @@ async function copyTemplate(options) {
29103
29220
  generatePnpmWorkspaceYaml(),
29104
29221
  "utf-8"
29105
29222
  );
29223
+ await restoreIgnoreFile(targetDir);
29224
+ await writeScaffoldNpmrc(targetDir, packageManager);
29106
29225
  if (database.type === "sqlite") {
29107
29226
  await import_fs_extra8.default.ensureDir(path15__default.default.join(targetDir, "data"));
29108
29227
  }
@@ -29118,7 +29237,7 @@ async function copyTemplate(options) {
29118
29237
  await replacePlaceholders(targetDir, placeholders);
29119
29238
  }
29120
29239
  async function copyPluginTemplate(opts) {
29121
- const { projectName, typeDir, targetDir, useYalc } = opts;
29240
+ const { projectName, typeDir, targetDir, useYalc, packageManager } = opts;
29122
29241
  if (!await import_fs_extra8.default.pathExists(typeDir)) {
29123
29242
  throw new Error(
29124
29243
  `Plugin template not found at ${typeDir}. The package may be corrupted or the download failed.`
@@ -29147,6 +29266,8 @@ async function copyPluginTemplate(opts) {
29147
29266
  generatePnpmWorkspaceYaml(),
29148
29267
  "utf-8"
29149
29268
  );
29269
+ await restoreIgnoreFile(targetDir);
29270
+ await writeScaffoldNpmrc(targetDir, packageManager);
29150
29271
  const nextlyRange = await resolvePluginNextlyRange(useYalc);
29151
29272
  await replacePlaceholders(
29152
29273
  targetDir,
@@ -32727,6 +32848,7 @@ async function createNextly(options = {}) {
32727
32848
  } else {
32728
32849
  s3.start("Scaffolding project...");
32729
32850
  }
32851
+ const scaffoldPackageManager = await detectPackageManager2(targetDir);
32730
32852
  await copyTemplate({
32731
32853
  projectName,
32732
32854
  projectType,
@@ -32736,6 +32858,7 @@ async function createNextly(options = {}) {
32736
32858
  useYalc,
32737
32859
  approach,
32738
32860
  templateSource,
32861
+ packageManager: scaffoldPackageManager,
32739
32862
  // Suppress copyTemplate's "directory already exists" guard when the
32740
32863
  // installer has already negotiated the conflict with the user
32741
32864
  // (either by emptying the dir or accepting an overlay). Without