@shipindays/shipindays 0.1.14 → 0.1.15

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.
Files changed (2) hide show
  1. package/index.js +53 -63
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -125,10 +125,12 @@ const ENV_VARS = {
125
125
  },
126
126
  };
127
127
 
128
- // Recursively copy a directory, skipping unwanted folders.
129
- // We do this manually instead of fs.copy(filter) because fs-extra's filter
130
- // callback skips the root directory itself on some platforms when the
131
- // target doesn't exist yet, resulting in nothing being copied.
128
+ /**
129
+ * RECURSIVE DIRECTORY MERGE
130
+ * * This function walks through the source directory and copies files to the destination.
131
+ * If a directory exists in both places, it dives deeper to merge contents rather
132
+ * than replacing the entire folder.
133
+ */
132
134
  async function copyDir(src, dest, skipNames = []) {
133
135
  await fs.ensureDir(dest);
134
136
  const entries = await fs.readdir(src, { withFileTypes: true });
@@ -140,71 +142,68 @@ async function copyDir(src, dest, skipNames = []) {
140
142
  const destPath = path.join(dest, entry.name);
141
143
 
142
144
  if (entry.isDirectory()) {
143
- // Create sub-directory in destination and recurse
144
- await fs.ensureDir(destPath);
145
+ // If it's a directory, recurse into it to ensure we don't
146
+ // overwrite existing folders in the target, but merge into them.
145
147
  await copyDir(srcPath, destPath, skipNames);
146
148
  } else {
147
- // Copy the file
149
+ // If it's a file, copy/overwrite it into the target.
148
150
  await fs.copy(srcPath, destPath, { overwrite: true });
149
151
  }
150
152
  }
151
153
  }
152
154
 
153
- // Each block lives at templates/blocks/<feature>/<provider>/
154
- // Its src/ folder is copied on top of the project's src/.
155
- // To add a new provider: create the folder, match exported function names,
156
- // add a package.json with extra deps, register it in the provider map above,
157
- // and add env vars below.
155
+ /**
156
+ * BLOCK INJECTION LOGIC
157
+ * * Improved to handle deep nesting. It takes everything inside the provider folder
158
+ * (except package.json and node_modules) and merges it into the project root.
159
+ */
158
160
  async function injectBlock(feature, provider, targetPath) {
159
161
  const blockRoot = path.join(BLOCKS_DIR, feature, provider);
160
- const blockSrcDir = path.join(blockRoot, "src");
161
162
 
162
163
  if (!await fs.pathExists(blockRoot)) {
163
- throw new Error(`Block not found: ${blockRoot}`);
164
+ throw new Error(`Block folder missing: ${blockRoot}`);
164
165
  }
165
166
 
166
- // 1. Copy everything inside block/src/ to target/src/
167
- if (await fs.pathExists(blockSrcDir)) {
168
- // We skip node_modules and .next if they accidentally exist in the block
169
- await copyDir(blockSrcDir, path.join(targetPath, "src"), ["node_modules", ".next"]);
170
- }
171
-
172
- // 2. Handle non-src files (like public/ or drizzle/ if the block has them)
173
- // Check if there are other folders in the block root that aren't 'src' or 'package.json'
174
167
  const blockEntries = await fs.readdir(blockRoot, { withFileTypes: true });
168
+
175
169
  for (const entry of blockEntries) {
176
- if (entry.isDirectory() && entry.name !== "src" && entry.name !== "node_modules") {
177
- await copyDir(
178
- path.join(blockRoot, entry.name),
179
- path.join(targetPath, entry.name)
180
- );
170
+ // 1. Skip package.json (handled by mergePackageJson)
171
+ // 2. Skip node_modules or .next if they exist in the template
172
+ if (
173
+ entry.name === "package.json" ||
174
+ entry.name === "node_modules" ||
175
+ entry.name === ".next"
176
+ ) continue;
177
+
178
+ const srcPath = path.join(blockRoot, entry.name);
179
+ const destPath = path.join(targetPath, entry.name);
180
+
181
+ if (entry.isDirectory()) {
182
+ // This will now correctly merge 'src', 'public', 'hooks', etc.
183
+ // because copyDir is recursive.
184
+ await copyDir(srcPath, destPath, ["node_modules", ".next"]);
185
+ } else {
186
+ await fs.copy(srcPath, destPath, { overwrite: true });
181
187
  }
182
188
  }
183
189
  }
184
190
 
185
- // Reads the block's package.json and merges its deps into the project's package.json.
186
- // The block's package.json is never copied as a file — only read for merging.
191
+ /**
192
+ * PACKAGE.JSON MERGER
193
+ * * Deep merges dependencies and devDependencies so the final project
194
+ * has all required libraries from every selected block.
195
+ */
187
196
  async function mergePackageJson(targetPath, feature, provider) {
188
197
  const blockPkgPath = path.join(BLOCKS_DIR, feature, provider, "package.json");
189
198
  const targetPkgPath = path.join(targetPath, "package.json");
190
199
 
191
- if (!await fs.pathExists(blockPkgPath)) return;
192
- if (!await fs.pathExists(targetPkgPath)) return;
200
+ if (!await fs.pathExists(blockPkgPath) || !await fs.pathExists(targetPkgPath)) return;
193
201
 
194
202
  const targetPkg = await fs.readJson(targetPkgPath);
195
203
  const blockPkg = await fs.readJson(blockPkgPath);
196
204
 
197
- // Merge dependencies
198
- targetPkg.dependencies = {
199
- ...(targetPkg.dependencies ?? {}),
200
- ...(blockPkg.dependencies ?? {}),
201
- };
202
-
203
- // Merge devDependencies
204
- targetPkg.devDependencies = {
205
- ...(targetPkg.devDependencies ?? {}),
206
- ...(blockPkg.devDependencies ?? {}),
207
- };
205
+ targetPkg.dependencies = { ...(targetPkg.dependencies ?? {}), ...(blockPkg.dependencies ?? {}) };
206
+ targetPkg.devDependencies = { ...(targetPkg.devDependencies ?? {}), ...(blockPkg.devDependencies ?? {}) };
208
207
 
209
208
  await fs.writeJson(targetPkgPath, targetPkg, { spaces: 2 });
210
209
  }
@@ -388,30 +387,21 @@ async function main() {
388
387
 
389
388
  // 5. Copy base template
390
389
  spin.start("Copying base template...");
391
- if (!await fs.pathExists(BASE_DIR)) {
392
- spin.stop(chalk.red(`Base template not found: ${BASE_DIR}`));
393
- process.exit(1);
394
- }
395
390
  await copyDir(BASE_DIR, targetPath, ["node_modules", ".next", ".turbo"]);
396
391
  spin.stop("Base template copied.");
397
392
 
398
- // 6. Inject auth block
399
- spin.start(`Injecting auth: ${choices.auth}...`);
400
- await injectBlock("auth", choices.auth, targetPath);
401
- await mergePackageJson(targetPath, "auth", choices.auth);
402
- spin.stop(`Auth: ${choices.auth} ✓`);
403
-
404
- // 7. Inject email block
405
- spin.start(`Injecting email: ${choices.email}...`);
406
- await injectBlock("email", choices.email, targetPath);
407
- await mergePackageJson(targetPath, "email", choices.email);
408
- spin.stop(`Email: ${choices.email} ✓`);
409
-
410
- // 7. Inject payment block
411
- spin.start(`Injecting payments: ${choices.payments}...`);
412
- await injectBlock("payments", choices.payments, targetPath);
413
- await mergePackageJson(targetPath, "payments", choices.payments);
414
- spin.stop(`Payments: ${choices.payments} ✓`);
393
+ // 6. Inject Blocks
394
+ // The injectBlock function now handles the internal recursion correctly.
395
+ const features = ["auth", "email", "payments"];
396
+ for (const feature of features) {
397
+ const provider = choices[feature];
398
+ if (provider) {
399
+ spin.start(`Injecting ${feature}: ${provider}...`);
400
+ await injectBlock(feature, provider, targetPath);
401
+ await mergePackageJson(targetPath, feature, provider);
402
+ spin.stop(`${feature} injected ✓`);
403
+ }
404
+ }
415
405
 
416
406
  // 8. Write .env.example
417
407
  spin.start("Writing .env.example...");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipindays/shipindays",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "Open source CLI to scaffold a Next.js SaaS. Pick your auth, email, and payments wired up and ready to ship.",
5
5
  "type": "module",
6
6
  "bin": {