@shipindays/shipindays 0.1.4 → 0.1.6
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 +1 -1
- package/index.js +66 -64
- package/package.json +1 -1
- package/templates/base/AGENTS.md +5 -0
- package/templates/base/CLAUDE.md +1 -0
- package/templates/base/README.md +36 -0
- package/templates/base/eslint.config.mjs +18 -0
- package/templates/base/next.config.ts +7 -0
- package/templates/base/package-lock.json +6701 -0
- package/templates/base/package.json +26 -0
- package/templates/base/postcss.config.mjs +7 -0
- package/templates/base/public/file.svg +1 -0
- package/templates/base/public/globe.svg +1 -0
- package/templates/base/public/next.svg +1 -0
- package/templates/base/public/vercel.svg +1 -0
- package/templates/base/public/window.svg +1 -0
- package/templates/base/src/app/(auth)/login/page.tsx +114 -0
- package/templates/base/src/app/(auth)/signup/page.tsx +130 -0
- package/templates/base/src/app/dashboard/page.tsx +59 -0
- package/templates/base/src/app/favicon.ico +0 -0
- package/templates/base/src/app/globals.css +26 -0
- package/templates/base/src/app/layout.tsx +33 -0
- package/templates/base/src/app/page.tsx +46 -0
- package/templates/base/src/components/auth/logout-button.tsx +36 -0
- package/templates/base/src/components/branding/powered-by-shipindays.tsx +39 -0
- package/templates/base/src/lib/auth/index.ts +24 -0
- package/templates/base/src/middleware.ts +14 -0
- package/templates/base/tsconfig.json +34 -0
package/README.md
CHANGED
package/index.js
CHANGED
|
@@ -20,7 +20,7 @@ const BLOCKS_DIR = path.join(TEMPLATES_DIR, "blocks");
|
|
|
20
20
|
|
|
21
21
|
function printBanner() {
|
|
22
22
|
console.log("\n");
|
|
23
|
-
console.log(chalk.green(" shipindays
|
|
23
|
+
console.log(chalk.green(" ⚡ shipindays"));
|
|
24
24
|
console.log(chalk.dim(" Ship your SaaS in days, not months."));
|
|
25
25
|
console.log(chalk.dim(" https://shipindays.nikhilsai.com\n"));
|
|
26
26
|
}
|
|
@@ -72,7 +72,9 @@ const EMAIL_PROVIDERS = {
|
|
|
72
72
|
|
|
73
73
|
const ENV_VARS = {
|
|
74
74
|
base: {
|
|
75
|
-
"# App": [
|
|
75
|
+
"# App": [
|
|
76
|
+
"NEXT_PUBLIC_APP_URL=http://localhost:3000",
|
|
77
|
+
],
|
|
76
78
|
},
|
|
77
79
|
|
|
78
80
|
auth: {
|
|
@@ -84,19 +86,26 @@ const ENV_VARS = {
|
|
|
84
86
|
],
|
|
85
87
|
},
|
|
86
88
|
nextauth: {
|
|
87
|
-
"# NextAuth": [
|
|
89
|
+
"# NextAuth": [
|
|
90
|
+
"AUTH_SECRET=",
|
|
91
|
+
"NEXTAUTH_URL=http://localhost:3000",
|
|
92
|
+
],
|
|
88
93
|
"# OAuth — Google (console.cloud.google.com)": [
|
|
89
94
|
"AUTH_GOOGLE_ID=",
|
|
90
95
|
"AUTH_GOOGLE_SECRET=",
|
|
91
96
|
],
|
|
92
|
-
"# OAuth — GitHub (github.com → Settings → Developer settings → OAuth Apps)":
|
|
93
|
-
|
|
97
|
+
"# OAuth — GitHub (github.com → Settings → Developer settings → OAuth Apps)": [
|
|
98
|
+
"AUTH_GITHUB_ID=",
|
|
99
|
+
"AUTH_GITHUB_SECRET=",
|
|
100
|
+
],
|
|
94
101
|
},
|
|
95
102
|
},
|
|
96
103
|
|
|
97
104
|
email: {
|
|
98
105
|
resend: {
|
|
99
|
-
"# Resend (resend.com → API Keys)": [
|
|
106
|
+
"# Resend (resend.com → API Keys)": [
|
|
107
|
+
"RESEND_API_KEY=",
|
|
108
|
+
],
|
|
100
109
|
},
|
|
101
110
|
nodemailer: {
|
|
102
111
|
"# SMTP / Nodemailer": [
|
|
@@ -148,19 +157,19 @@ async function injectBlock(feature, provider, targetPath) {
|
|
|
148
157
|
const blockSrcDir = path.join(blockRoot, "src");
|
|
149
158
|
|
|
150
159
|
// Check block folder exists
|
|
151
|
-
if (!
|
|
160
|
+
if (!await fs.pathExists(blockRoot)) {
|
|
152
161
|
throw new Error(
|
|
153
162
|
`Block not found: ${blockRoot}\n` +
|
|
154
|
-
|
|
163
|
+
`Make sure templates/blocks/${feature}/${provider}/ exists.`
|
|
155
164
|
);
|
|
156
165
|
}
|
|
157
166
|
|
|
158
167
|
// Check src/ folder exists inside block
|
|
159
|
-
if (!
|
|
168
|
+
if (!await fs.pathExists(blockSrcDir)) {
|
|
160
169
|
throw new Error(
|
|
161
170
|
`Block src/ folder missing: ${blockSrcDir}\n` +
|
|
162
|
-
|
|
163
|
-
|
|
171
|
+
`Every block must have a src/ folder.\n` +
|
|
172
|
+
`Structure: templates/blocks/${feature}/${provider}/src/...`
|
|
164
173
|
);
|
|
165
174
|
}
|
|
166
175
|
|
|
@@ -169,7 +178,9 @@ async function injectBlock(feature, provider, targetPath) {
|
|
|
169
178
|
// - New files → created fresh in the project
|
|
170
179
|
await fs.copy(blockSrcDir, path.join(targetPath, "src"), {
|
|
171
180
|
overwrite: true,
|
|
172
|
-
filter: (src) =>
|
|
181
|
+
filter: (src) =>
|
|
182
|
+
!src.includes("node_modules") &&
|
|
183
|
+
!src.includes(".next"),
|
|
173
184
|
});
|
|
174
185
|
}
|
|
175
186
|
|
|
@@ -184,8 +195,8 @@ async function mergePackageJson(targetPath, feature, provider) {
|
|
|
184
195
|
const blockPkgPath = path.join(BLOCKS_DIR, feature, provider, "package.json");
|
|
185
196
|
const targetPkgPath = path.join(targetPath, "package.json");
|
|
186
197
|
|
|
187
|
-
if (!
|
|
188
|
-
if (!
|
|
198
|
+
if (!await fs.pathExists(blockPkgPath)) return;
|
|
199
|
+
if (!await fs.pathExists(targetPkgPath)) return;
|
|
189
200
|
|
|
190
201
|
const targetPkg = await fs.readJson(targetPkgPath);
|
|
191
202
|
const blockPkg = await fs.readJson(blockPkgPath);
|
|
@@ -233,16 +244,10 @@ function buildEnvExample(choices) {
|
|
|
233
244
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
234
245
|
// HELPERS
|
|
235
246
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
236
|
-
function isValidName(n) {
|
|
237
|
-
return /^[a-zA-Z0-9-_]+$/.test(n);
|
|
238
|
-
}
|
|
247
|
+
function isValidName(n) { return /^[a-zA-Z0-9-_]+$/.test(n); }
|
|
239
248
|
|
|
240
249
|
function toSlug(s) {
|
|
241
|
-
return s
|
|
242
|
-
.trim()
|
|
243
|
-
.toLowerCase()
|
|
244
|
-
.replace(/\s+/g, "-")
|
|
245
|
-
.replace(/[^a-z0-9-_]/g, "");
|
|
250
|
+
return s.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-_]/g, "");
|
|
246
251
|
}
|
|
247
252
|
|
|
248
253
|
function detectPM() {
|
|
@@ -250,9 +255,7 @@ function detectPM() {
|
|
|
250
255
|
return a.includes("pnpm") ? "pnpm" : a.includes("yarn") ? "yarn" : "npm";
|
|
251
256
|
}
|
|
252
257
|
|
|
253
|
-
function run(cmd, cwd) {
|
|
254
|
-
execSync(cmd, { cwd, stdio: "inherit" });
|
|
255
|
-
}
|
|
258
|
+
function run(cmd, cwd) { execSync(cmd, { cwd, stdio: "inherit" }); }
|
|
256
259
|
|
|
257
260
|
function buildGitignore() {
|
|
258
261
|
return [
|
|
@@ -303,9 +306,7 @@ function printNextSteps(projectDir, pm, choices) {
|
|
|
303
306
|
s(`${runCmd} db:push`);
|
|
304
307
|
s(`${runCmd} dev`);
|
|
305
308
|
console.log("");
|
|
306
|
-
console.log(
|
|
307
|
-
chalk.dim(" GitHub → https://github.com/nikhilsaiankilla/shipindays"),
|
|
308
|
-
);
|
|
309
|
+
console.log(chalk.dim(" GitHub → https://github.com/nikhilsaiankilla/shipindays"));
|
|
309
310
|
console.log(chalk.dim(" Twitter → https://x.com/itzznikhilsai"));
|
|
310
311
|
console.log("");
|
|
311
312
|
console.log(chalk.green(" ⚡ Now go build what only you can build.\n"));
|
|
@@ -333,10 +334,7 @@ async function main() {
|
|
|
333
334
|
}
|
|
334
335
|
},
|
|
335
336
|
});
|
|
336
|
-
if (p.isCancel(answer)) {
|
|
337
|
-
p.cancel("Cancelled.");
|
|
338
|
-
process.exit(0);
|
|
339
|
-
}
|
|
337
|
+
if (p.isCancel(answer)) { p.cancel("Cancelled."); process.exit(0); }
|
|
340
338
|
projectDir = answer;
|
|
341
339
|
}
|
|
342
340
|
|
|
@@ -348,17 +346,14 @@ async function main() {
|
|
|
348
346
|
? path.basename(process.cwd())
|
|
349
347
|
: toSlug(path.basename(projectDir.replace(/^\.\//, "")));
|
|
350
348
|
|
|
351
|
-
if (!isCurrentDir &&
|
|
352
|
-
const files = (await fs.readdir(targetPath)).filter(
|
|
349
|
+
if (!isCurrentDir && await fs.pathExists(targetPath)) {
|
|
350
|
+
const files = (await fs.readdir(targetPath)).filter(f => f !== ".git");
|
|
353
351
|
if (files.length > 0) {
|
|
354
352
|
const ok = await p.confirm({
|
|
355
353
|
message: `${projectDir} is not empty. Overwrite?`,
|
|
356
354
|
initialValue: false,
|
|
357
355
|
});
|
|
358
|
-
if (p.isCancel(ok) || !ok) {
|
|
359
|
-
p.cancel("Cancelled.");
|
|
360
|
-
process.exit(0);
|
|
361
|
-
}
|
|
356
|
+
if (p.isCancel(ok) || !ok) { p.cancel("Cancelled."); process.exit(0); }
|
|
362
357
|
}
|
|
363
358
|
}
|
|
364
359
|
|
|
@@ -366,52 +361,44 @@ async function main() {
|
|
|
366
361
|
const authProvider = await p.select({
|
|
367
362
|
message: "Auth provider",
|
|
368
363
|
options: Object.entries(AUTH_PROVIDERS).map(([value, { label, hint }]) => ({
|
|
369
|
-
value,
|
|
370
|
-
label,
|
|
371
|
-
hint,
|
|
364
|
+
value, label, hint,
|
|
372
365
|
})),
|
|
373
366
|
});
|
|
374
|
-
if (p.isCancel(authProvider)) {
|
|
375
|
-
p.cancel("Cancelled.");
|
|
376
|
-
process.exit(0);
|
|
377
|
-
}
|
|
367
|
+
if (p.isCancel(authProvider)) { p.cancel("Cancelled."); process.exit(0); }
|
|
378
368
|
|
|
379
369
|
// ── 3. Pick email provider ─────────────────────────────────────────────────
|
|
380
370
|
const emailProvider = await p.select({
|
|
381
371
|
message: "Email provider",
|
|
382
|
-
options: Object.entries(EMAIL_PROVIDERS).map(
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
label,
|
|
386
|
-
hint,
|
|
387
|
-
}),
|
|
388
|
-
),
|
|
372
|
+
options: Object.entries(EMAIL_PROVIDERS).map(([value, { label, hint }]) => ({
|
|
373
|
+
value, label, hint,
|
|
374
|
+
})),
|
|
389
375
|
});
|
|
390
|
-
if (p.isCancel(emailProvider)) {
|
|
391
|
-
p.cancel("Cancelled.");
|
|
392
|
-
process.exit(0);
|
|
393
|
-
}
|
|
376
|
+
if (p.isCancel(emailProvider)) { p.cancel("Cancelled."); process.exit(0); }
|
|
394
377
|
|
|
395
378
|
const choices = {
|
|
396
379
|
auth: authProvider,
|
|
397
380
|
email: emailProvider,
|
|
398
381
|
};
|
|
399
382
|
|
|
383
|
+
// ── 4. Git + install ───────────────────────────────────────────────────────
|
|
384
|
+
const initGit = await p.confirm({
|
|
385
|
+
message: "Initialize a git repository?",
|
|
386
|
+
initialValue: true,
|
|
387
|
+
});
|
|
388
|
+
if (p.isCancel(initGit)) { p.cancel("Cancelled."); process.exit(0); }
|
|
389
|
+
|
|
400
390
|
const pm = detectPM();
|
|
401
391
|
const install = await p.confirm({
|
|
402
392
|
message: `Install dependencies with ${pm}?`,
|
|
403
393
|
initialValue: true,
|
|
404
394
|
});
|
|
405
|
-
if (p.isCancel(install)) {
|
|
406
|
-
p.cancel("Cancelled.");
|
|
407
|
-
process.exit(0);
|
|
408
|
-
}
|
|
395
|
+
if (p.isCancel(install)) { p.cancel("Cancelled."); process.exit(0); }
|
|
409
396
|
|
|
410
397
|
const spin = p.spinner();
|
|
411
398
|
|
|
412
399
|
// ── 5. Copy base template ──────────────────────────────────────────────────
|
|
413
400
|
spin.start("Copying base template...");
|
|
414
|
-
if (!
|
|
401
|
+
if (!await fs.pathExists(BASE_DIR)) {
|
|
415
402
|
spin.stop(chalk.red(`Base template not found: ${BASE_DIR}`));
|
|
416
403
|
process.exit(1);
|
|
417
404
|
}
|
|
@@ -446,7 +433,7 @@ async function main() {
|
|
|
446
433
|
spin.start("Writing .env.example...");
|
|
447
434
|
await fs.outputFile(
|
|
448
435
|
path.join(targetPath, ".env.example"),
|
|
449
|
-
buildEnvExample(choices)
|
|
436
|
+
buildEnvExample(choices)
|
|
450
437
|
);
|
|
451
438
|
spin.stop(".env.example written.");
|
|
452
439
|
|
|
@@ -463,12 +450,27 @@ async function main() {
|
|
|
463
450
|
}
|
|
464
451
|
spin.stop("package.json configured.");
|
|
465
452
|
|
|
453
|
+
// ── 11. Git init ───────────────────────────────────────────────────────────
|
|
454
|
+
if (initGit) {
|
|
455
|
+
spin.start("Initialising git...");
|
|
456
|
+
try {
|
|
457
|
+
run("git init", targetPath);
|
|
458
|
+
run("git add -A", targetPath);
|
|
459
|
+
run(`git commit -m "chore: scaffold from shipindays"`, targetPath);
|
|
460
|
+
spin.stop("Git initialised.");
|
|
461
|
+
} catch {
|
|
462
|
+
spin.stop(chalk.yellow("Git skipped — run manually."));
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
466
|
// ── 12. Install dependencies ───────────────────────────────────────────────
|
|
467
467
|
if (install) {
|
|
468
468
|
spin.start(`Installing with ${pm}...`);
|
|
469
469
|
try {
|
|
470
470
|
const cmd =
|
|
471
|
-
pm === "yarn" ? "yarn" :
|
|
471
|
+
pm === "yarn" ? "yarn" :
|
|
472
|
+
pm === "pnpm" ? "pnpm install" :
|
|
473
|
+
"npm install";
|
|
472
474
|
run(cmd, targetPath);
|
|
473
475
|
spin.stop("Dependencies installed.");
|
|
474
476
|
} catch {
|
|
@@ -483,4 +485,4 @@ async function main() {
|
|
|
483
485
|
main().catch((err) => {
|
|
484
486
|
console.error(chalk.red("\n Fatal: " + err.message));
|
|
485
487
|
process.exit(1);
|
|
486
|
-
});
|
|
488
|
+
});
|
package/package.json
CHANGED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<!-- BEGIN:nextjs-agent-rules -->
|
|
2
|
+
# This is NOT the Next.js you know
|
|
3
|
+
|
|
4
|
+
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
|
|
5
|
+
<!-- END:nextjs-agent-rules -->
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
|
2
|
+
|
|
3
|
+
## Getting Started
|
|
4
|
+
|
|
5
|
+
First, run the development server:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm run dev
|
|
9
|
+
# or
|
|
10
|
+
yarn dev
|
|
11
|
+
# or
|
|
12
|
+
pnpm dev
|
|
13
|
+
# or
|
|
14
|
+
bun dev
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
|
18
|
+
|
|
19
|
+
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
|
20
|
+
|
|
21
|
+
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
|
22
|
+
|
|
23
|
+
## Learn More
|
|
24
|
+
|
|
25
|
+
To learn more about Next.js, take a look at the following resources:
|
|
26
|
+
|
|
27
|
+
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
|
28
|
+
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
|
29
|
+
|
|
30
|
+
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
|
31
|
+
|
|
32
|
+
## Deploy on Vercel
|
|
33
|
+
|
|
34
|
+
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
|
35
|
+
|
|
36
|
+
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineConfig, globalIgnores } from "eslint/config";
|
|
2
|
+
import nextVitals from "eslint-config-next/core-web-vitals";
|
|
3
|
+
import nextTs from "eslint-config-next/typescript";
|
|
4
|
+
|
|
5
|
+
const eslintConfig = defineConfig([
|
|
6
|
+
...nextVitals,
|
|
7
|
+
...nextTs,
|
|
8
|
+
// Override default ignores of eslint-config-next.
|
|
9
|
+
globalIgnores([
|
|
10
|
+
// Default ignores of eslint-config-next:
|
|
11
|
+
".next/**",
|
|
12
|
+
"out/**",
|
|
13
|
+
"build/**",
|
|
14
|
+
"next-env.d.ts",
|
|
15
|
+
]),
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
export default eslintConfig;
|