create-lacspace-app 2.12.0 → 2.13.1
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 +2 -0
- package/dist/index.js +86 -10
- package/dist/lib.cjs +82 -6
- package/dist/lib.js +86 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -202,6 +202,8 @@ Every template is Next.js 15 App Router + React 19 + Tailwind v4 — dark, moder
|
|
|
202
202
|
| `--no-install` | skip installing dependencies |
|
|
203
203
|
| `--no-git` | skip git init |
|
|
204
204
|
| `-y, --yes` | accept defaults (needs a project name) |
|
|
205
|
+
| `-h, --help` | show every flag |
|
|
206
|
+
| `-v, --version` | print the installed version |
|
|
205
207
|
|
|
206
208
|
### 🎨 Themes
|
|
207
209
|
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, readdirSync, mkdirSync, writeFileSync } from 'fs';
|
|
3
|
-
import {
|
|
2
|
+
import { realpathSync, existsSync, readdirSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
3
|
+
import { basename, resolve, join, dirname } from 'path';
|
|
4
4
|
import { createInterface } from 'readline/promises';
|
|
5
|
-
import {
|
|
5
|
+
import { stdout, exit, argv, stdin, cwd } from 'process';
|
|
6
6
|
import { spawnSync } from 'child_process';
|
|
7
|
-
import { pathToFileURL } from 'url';
|
|
7
|
+
import { pathToFileURL, fileURLToPath } from 'url';
|
|
8
8
|
|
|
9
9
|
var C = {
|
|
10
10
|
reset: "\x1B[0m",
|
|
@@ -7468,6 +7468,7 @@ var backendPkgJson = (ctx) => {
|
|
|
7468
7468
|
"@lacspace/id": "^1.1.0",
|
|
7469
7469
|
"@lacspace/rate-limit": "^1.2.0",
|
|
7470
7470
|
"@lacspace/cache": "^1.1.0",
|
|
7471
|
+
"@lacspace/logger": "^1.0.0",
|
|
7471
7472
|
...featureDeps,
|
|
7472
7473
|
[`${scope(ctx)}/types`]: "*"
|
|
7473
7474
|
},
|
|
@@ -7577,21 +7578,54 @@ var backendIndex = (ctx) => `import "./load-env.js"; // MUST be first: fills pro
|
|
|
7577
7578
|
import { env } from "./env.js";
|
|
7578
7579
|
import { connectDb } from "./db.js";
|
|
7579
7580
|
import { createApp } from "./app.js";
|
|
7581
|
+
import { log } from "./logger.js";
|
|
7580
7582
|
|
|
7581
7583
|
// how this works: connect to MongoDB, build the Express app, then listen.
|
|
7582
7584
|
async function main(): Promise<void> {
|
|
7583
7585
|
await connectDb(env.MONGODB_URI);
|
|
7584
7586
|
const app = createApp();
|
|
7585
7587
|
app.listen(env.PORT, () => {
|
|
7586
|
-
|
|
7588
|
+
log.info("${ctx.name} API ready", { url: \`http://localhost:\${env.PORT}\` });
|
|
7587
7589
|
});
|
|
7588
7590
|
}
|
|
7589
7591
|
|
|
7590
7592
|
main().catch((err) => {
|
|
7591
|
-
|
|
7593
|
+
log.fatal("Failed to start the API", { err });
|
|
7592
7594
|
process.exit(1);
|
|
7593
7595
|
});
|
|
7594
7596
|
`;
|
|
7597
|
+
var backendLogger = () => `import { createLogger, jsonConsole, prettyConsole } from "@lacspace/logger";
|
|
7598
|
+
import type { Request, Response, NextFunction } from "express";
|
|
7599
|
+
import { env } from "./env.js";
|
|
7600
|
+
|
|
7601
|
+
// how this works: one structured logger for the whole API (@lacspace/logger, zero-dep).
|
|
7602
|
+
// In production we emit JSON-per-line (clean for log aggregators); in dev we print a
|
|
7603
|
+
// readable, coloured line. Common secret fields are redacted before anything is logged.
|
|
7604
|
+
const isProd = env.NODE_ENV === "production";
|
|
7605
|
+
export const log = createLogger({
|
|
7606
|
+
level: isProd ? "info" : "debug",
|
|
7607
|
+
transports: [isProd ? jsonConsole() : prettyConsole({ colors: true })],
|
|
7608
|
+
redact: ["password", "token", "authorization", "*.password", "*.token"],
|
|
7609
|
+
});
|
|
7610
|
+
|
|
7611
|
+
// Request logger \u2014 one line per request with method, path, status and duration.
|
|
7612
|
+
// Each request gets a child logger with a short request id you can thread through.
|
|
7613
|
+
export function requestLogger(req: Request, res: Response, next: NextFunction): void {
|
|
7614
|
+
const start = Date.now();
|
|
7615
|
+
const reqId = Math.random().toString(36).slice(2, 10);
|
|
7616
|
+
(req as Request & { log: typeof log }).log = log.child({ reqId });
|
|
7617
|
+
res.on("finish", () => {
|
|
7618
|
+
log.info("request", {
|
|
7619
|
+
reqId,
|
|
7620
|
+
method: req.method,
|
|
7621
|
+
path: req.originalUrl,
|
|
7622
|
+
status: res.statusCode,
|
|
7623
|
+
ms: Date.now() - start,
|
|
7624
|
+
});
|
|
7625
|
+
});
|
|
7626
|
+
next();
|
|
7627
|
+
}
|
|
7628
|
+
`;
|
|
7595
7629
|
var backendDb = () => `import mongoose from "mongoose";
|
|
7596
7630
|
|
|
7597
7631
|
// how this works: opens the Mongoose connection. Call once at boot.
|
|
@@ -7907,16 +7941,19 @@ export default router;
|
|
|
7907
7941
|
var backendApp = (ctx) => `import express from "express";
|
|
7908
7942
|
import cors from "cors";
|
|
7909
7943
|
import { env } from "./env.js";
|
|
7944
|
+
import { requestLogger } from "./logger.js";
|
|
7910
7945
|
import { errorHandler } from "./middleware/error.js";
|
|
7911
7946
|
import { registerRoutes } from "./routes/index.js";
|
|
7912
7947
|
|
|
7913
7948
|
// how this works: assembles the Express app \u2014 CORS for the frontend, JSON parsing,
|
|
7914
|
-
// a health check, all route groups
|
|
7949
|
+
// structured request logging (@lacspace/logger), a health check, all route groups
|
|
7950
|
+
// (see routes/index.ts), then the error handler LAST.
|
|
7915
7951
|
export function createApp(): express.Express {
|
|
7916
7952
|
const app = express();
|
|
7917
7953
|
|
|
7918
7954
|
app.use(cors({ origin: env.CORS_ORIGIN.split(",").map((o) => o.trim()), credentials: true }));
|
|
7919
7955
|
app.use(express.json());
|
|
7956
|
+
app.use(requestLogger); // one structured log line per request
|
|
7920
7957
|
|
|
7921
7958
|
app.get("/health", (_req, res) => { res.json({ ok: true, service: "${ctx.name}-api" }); });
|
|
7922
7959
|
registerRoutes(app);
|
|
@@ -7957,6 +7994,7 @@ function backendFiles(ctx) {
|
|
|
7957
7994
|
"backend/src/env.ts": backendEnv(ctx),
|
|
7958
7995
|
"backend/src/db.ts": backendDb(),
|
|
7959
7996
|
"backend/src/cache.ts": backendCache(),
|
|
7997
|
+
"backend/src/logger.ts": backendLogger(),
|
|
7960
7998
|
"backend/src/app.ts": backendApp(ctx),
|
|
7961
7999
|
"backend/src/http.ts": backendHttp(),
|
|
7962
8000
|
"backend/src/express.d.ts": backendExpressTypes(),
|
|
@@ -7976,9 +8014,24 @@ function backendFiles(ctx) {
|
|
|
7976
8014
|
}
|
|
7977
8015
|
return files;
|
|
7978
8016
|
}
|
|
8017
|
+
function cliVersion() {
|
|
8018
|
+
try {
|
|
8019
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
8020
|
+
for (const rel of ["../package.json", "./package.json", "../../package.json"]) {
|
|
8021
|
+
const file = resolve(here, rel);
|
|
8022
|
+
if (!existsSync(file)) continue;
|
|
8023
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
8024
|
+
const version = parsed.version;
|
|
8025
|
+
const name = parsed.name;
|
|
8026
|
+
if (typeof version === "string" && (name === "create-lacspace-app" || rel !== "../../package.json")) return version;
|
|
8027
|
+
}
|
|
8028
|
+
} catch {
|
|
8029
|
+
}
|
|
8030
|
+
return "unknown";
|
|
8031
|
+
}
|
|
7979
8032
|
var splitList = (s) => s.split(",").map((x) => x.trim()).filter(Boolean);
|
|
7980
8033
|
function parseArgs(list) {
|
|
7981
|
-
const a = { features: [], yes: false, install: true, git: true, pm: "npm", help: false, dryRun: false };
|
|
8034
|
+
const a = { features: [], yes: false, install: true, git: true, pm: "npm", help: false, version: false, dryRun: false, unknown: [] };
|
|
7982
8035
|
for (let i = 0; i < list.length; i++) {
|
|
7983
8036
|
const arg = list[i];
|
|
7984
8037
|
const next = () => list[++i] ?? "";
|
|
@@ -8001,6 +8054,7 @@ function parseArgs(list) {
|
|
|
8001
8054
|
} else if (arg === "--recipe") a.recipe = next();
|
|
8002
8055
|
else if (arg.startsWith("--recipe=")) a.recipe = arg.slice(9);
|
|
8003
8056
|
else if (arg === "-h" || arg === "--help") a.help = true;
|
|
8057
|
+
else if (arg === "-v" || arg === "-V" || arg === "--version") a.version = true;
|
|
8004
8058
|
else if (arg.startsWith("--template=")) a.template = arg.slice(11);
|
|
8005
8059
|
else if (arg.startsWith("--theme=")) a.theme = arg.slice(8);
|
|
8006
8060
|
else if (arg.startsWith("--accent=")) a.theme = arg.slice(9);
|
|
@@ -8008,6 +8062,7 @@ function parseArgs(list) {
|
|
|
8008
8062
|
else if (arg.startsWith("--with=")) a.features.push(...splitList(arg.slice(7)));
|
|
8009
8063
|
else if (arg.startsWith("--features=")) a.features.push(...splitList(arg.slice(11)));
|
|
8010
8064
|
else if (!arg.startsWith("-") && !a.name) a.name = arg;
|
|
8065
|
+
else if (arg.startsWith("-")) a.unknown.push(arg);
|
|
8011
8066
|
}
|
|
8012
8067
|
return a;
|
|
8013
8068
|
}
|
|
@@ -8115,6 +8170,7 @@ ${c("bold", "Options")}
|
|
|
8115
8170
|
--no-git Skip git init
|
|
8116
8171
|
-y, --yes Accept defaults (needs <name>)
|
|
8117
8172
|
-h, --help Show this help
|
|
8173
|
+
-v, --version Print the version and exit
|
|
8118
8174
|
|
|
8119
8175
|
${c("bold", "Feature add-ons")} ${c("dim", "(--with) \u2014 free & keyless, local by default")}
|
|
8120
8176
|
${FEATURES.map((f) => ` ${f.key.padEnd(11)} ${f.description}`).join("\n")}
|
|
@@ -8588,6 +8644,16 @@ async function main() {
|
|
|
8588
8644
|
stdout.write(HELP + "\n");
|
|
8589
8645
|
return;
|
|
8590
8646
|
}
|
|
8647
|
+
if (args.version) {
|
|
8648
|
+
stdout.write(`create-lacspace-app ${cliVersion()}
|
|
8649
|
+
`);
|
|
8650
|
+
return;
|
|
8651
|
+
}
|
|
8652
|
+
if (args.unknown.length) {
|
|
8653
|
+
stdout.write(c("yellow", `
|
|
8654
|
+
! Unknown option${args.unknown.length > 1 ? "s" : ""}: ${args.unknown.join(", ")} \u2014 ignoring. Run with --help to see every flag.
|
|
8655
|
+
`));
|
|
8656
|
+
}
|
|
8591
8657
|
stdout.write(`
|
|
8592
8658
|
${c("bold", c("magenta", "\u25C6 create-lacspace-app"))} ${c("dim", "\u2014 a gorgeous Next.js starter, batteries wired")}
|
|
8593
8659
|
|
|
@@ -8786,8 +8852,18 @@ ${c("bold", "Your feature add-ons")} ${c("dim", "\u2014 details in LEARN.md")}
|
|
|
8786
8852
|
|
|
8787
8853
|
`);
|
|
8788
8854
|
}
|
|
8789
|
-
|
|
8790
|
-
|
|
8855
|
+
function isDirectRun() {
|
|
8856
|
+
const entry = argv[1];
|
|
8857
|
+
if (!entry) return false;
|
|
8858
|
+
const here = import.meta.url;
|
|
8859
|
+
if (here === pathToFileURL(entry).href) return true;
|
|
8860
|
+
try {
|
|
8861
|
+
if (here === pathToFileURL(realpathSync(entry)).href) return true;
|
|
8862
|
+
} catch {
|
|
8863
|
+
}
|
|
8864
|
+
return basename(entry).replace(/\.[cm]?js$/, "") === "create-lacspace-app";
|
|
8865
|
+
}
|
|
8866
|
+
if (isDirectRun()) {
|
|
8791
8867
|
main().catch((err) => {
|
|
8792
8868
|
stdout.write(c("red", `
|
|
8793
8869
|
\u2717 ${err instanceof Error ? err.message : String(err)}
|
package/dist/lib.cjs
CHANGED
|
@@ -7472,6 +7472,7 @@ var backendPkgJson = (ctx) => {
|
|
|
7472
7472
|
"@lacspace/id": "^1.1.0",
|
|
7473
7473
|
"@lacspace/rate-limit": "^1.2.0",
|
|
7474
7474
|
"@lacspace/cache": "^1.1.0",
|
|
7475
|
+
"@lacspace/logger": "^1.0.0",
|
|
7475
7476
|
...featureDeps,
|
|
7476
7477
|
[`${scope(ctx)}/types`]: "*"
|
|
7477
7478
|
},
|
|
@@ -7581,21 +7582,54 @@ var backendIndex = (ctx) => `import "./load-env.js"; // MUST be first: fills pro
|
|
|
7581
7582
|
import { env } from "./env.js";
|
|
7582
7583
|
import { connectDb } from "./db.js";
|
|
7583
7584
|
import { createApp } from "./app.js";
|
|
7585
|
+
import { log } from "./logger.js";
|
|
7584
7586
|
|
|
7585
7587
|
// how this works: connect to MongoDB, build the Express app, then listen.
|
|
7586
7588
|
async function main(): Promise<void> {
|
|
7587
7589
|
await connectDb(env.MONGODB_URI);
|
|
7588
7590
|
const app = createApp();
|
|
7589
7591
|
app.listen(env.PORT, () => {
|
|
7590
|
-
|
|
7592
|
+
log.info("${ctx.name} API ready", { url: \`http://localhost:\${env.PORT}\` });
|
|
7591
7593
|
});
|
|
7592
7594
|
}
|
|
7593
7595
|
|
|
7594
7596
|
main().catch((err) => {
|
|
7595
|
-
|
|
7597
|
+
log.fatal("Failed to start the API", { err });
|
|
7596
7598
|
process.exit(1);
|
|
7597
7599
|
});
|
|
7598
7600
|
`;
|
|
7601
|
+
var backendLogger = () => `import { createLogger, jsonConsole, prettyConsole } from "@lacspace/logger";
|
|
7602
|
+
import type { Request, Response, NextFunction } from "express";
|
|
7603
|
+
import { env } from "./env.js";
|
|
7604
|
+
|
|
7605
|
+
// how this works: one structured logger for the whole API (@lacspace/logger, zero-dep).
|
|
7606
|
+
// In production we emit JSON-per-line (clean for log aggregators); in dev we print a
|
|
7607
|
+
// readable, coloured line. Common secret fields are redacted before anything is logged.
|
|
7608
|
+
const isProd = env.NODE_ENV === "production";
|
|
7609
|
+
export const log = createLogger({
|
|
7610
|
+
level: isProd ? "info" : "debug",
|
|
7611
|
+
transports: [isProd ? jsonConsole() : prettyConsole({ colors: true })],
|
|
7612
|
+
redact: ["password", "token", "authorization", "*.password", "*.token"],
|
|
7613
|
+
});
|
|
7614
|
+
|
|
7615
|
+
// Request logger \u2014 one line per request with method, path, status and duration.
|
|
7616
|
+
// Each request gets a child logger with a short request id you can thread through.
|
|
7617
|
+
export function requestLogger(req: Request, res: Response, next: NextFunction): void {
|
|
7618
|
+
const start = Date.now();
|
|
7619
|
+
const reqId = Math.random().toString(36).slice(2, 10);
|
|
7620
|
+
(req as Request & { log: typeof log }).log = log.child({ reqId });
|
|
7621
|
+
res.on("finish", () => {
|
|
7622
|
+
log.info("request", {
|
|
7623
|
+
reqId,
|
|
7624
|
+
method: req.method,
|
|
7625
|
+
path: req.originalUrl,
|
|
7626
|
+
status: res.statusCode,
|
|
7627
|
+
ms: Date.now() - start,
|
|
7628
|
+
});
|
|
7629
|
+
});
|
|
7630
|
+
next();
|
|
7631
|
+
}
|
|
7632
|
+
`;
|
|
7599
7633
|
var backendDb = () => `import mongoose from "mongoose";
|
|
7600
7634
|
|
|
7601
7635
|
// how this works: opens the Mongoose connection. Call once at boot.
|
|
@@ -7911,16 +7945,19 @@ export default router;
|
|
|
7911
7945
|
var backendApp = (ctx) => `import express from "express";
|
|
7912
7946
|
import cors from "cors";
|
|
7913
7947
|
import { env } from "./env.js";
|
|
7948
|
+
import { requestLogger } from "./logger.js";
|
|
7914
7949
|
import { errorHandler } from "./middleware/error.js";
|
|
7915
7950
|
import { registerRoutes } from "./routes/index.js";
|
|
7916
7951
|
|
|
7917
7952
|
// how this works: assembles the Express app \u2014 CORS for the frontend, JSON parsing,
|
|
7918
|
-
// a health check, all route groups
|
|
7953
|
+
// structured request logging (@lacspace/logger), a health check, all route groups
|
|
7954
|
+
// (see routes/index.ts), then the error handler LAST.
|
|
7919
7955
|
export function createApp(): express.Express {
|
|
7920
7956
|
const app = express();
|
|
7921
7957
|
|
|
7922
7958
|
app.use(cors({ origin: env.CORS_ORIGIN.split(",").map((o) => o.trim()), credentials: true }));
|
|
7923
7959
|
app.use(express.json());
|
|
7960
|
+
app.use(requestLogger); // one structured log line per request
|
|
7924
7961
|
|
|
7925
7962
|
app.get("/health", (_req, res) => { res.json({ ok: true, service: "${ctx.name}-api" }); });
|
|
7926
7963
|
registerRoutes(app);
|
|
@@ -7961,6 +7998,7 @@ function backendFiles(ctx) {
|
|
|
7961
7998
|
"backend/src/env.ts": backendEnv(ctx),
|
|
7962
7999
|
"backend/src/db.ts": backendDb(),
|
|
7963
8000
|
"backend/src/cache.ts": backendCache(),
|
|
8001
|
+
"backend/src/logger.ts": backendLogger(),
|
|
7964
8002
|
"backend/src/app.ts": backendApp(ctx),
|
|
7965
8003
|
"backend/src/http.ts": backendHttp(),
|
|
7966
8004
|
"backend/src/express.d.ts": backendExpressTypes(),
|
|
@@ -7980,9 +8018,24 @@ function backendFiles(ctx) {
|
|
|
7980
8018
|
}
|
|
7981
8019
|
return files;
|
|
7982
8020
|
}
|
|
8021
|
+
function cliVersion() {
|
|
8022
|
+
try {
|
|
8023
|
+
const here = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('lib.cjs', document.baseURI).href))));
|
|
8024
|
+
for (const rel of ["../package.json", "./package.json", "../../package.json"]) {
|
|
8025
|
+
const file = path.resolve(here, rel);
|
|
8026
|
+
if (!fs.existsSync(file)) continue;
|
|
8027
|
+
const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
8028
|
+
const version = parsed.version;
|
|
8029
|
+
const name = parsed.name;
|
|
8030
|
+
if (typeof version === "string" && (name === "create-lacspace-app" || rel !== "../../package.json")) return version;
|
|
8031
|
+
}
|
|
8032
|
+
} catch {
|
|
8033
|
+
}
|
|
8034
|
+
return "unknown";
|
|
8035
|
+
}
|
|
7983
8036
|
var splitList = (s) => s.split(",").map((x) => x.trim()).filter(Boolean);
|
|
7984
8037
|
function parseArgs(list) {
|
|
7985
|
-
const a = { features: [], yes: false, install: true, git: true, pm: "npm", help: false, dryRun: false };
|
|
8038
|
+
const a = { features: [], yes: false, install: true, git: true, pm: "npm", help: false, version: false, dryRun: false, unknown: [] };
|
|
7986
8039
|
for (let i = 0; i < list.length; i++) {
|
|
7987
8040
|
const arg = list[i];
|
|
7988
8041
|
const next = () => list[++i] ?? "";
|
|
@@ -8005,6 +8058,7 @@ function parseArgs(list) {
|
|
|
8005
8058
|
} else if (arg === "--recipe") a.recipe = next();
|
|
8006
8059
|
else if (arg.startsWith("--recipe=")) a.recipe = arg.slice(9);
|
|
8007
8060
|
else if (arg === "-h" || arg === "--help") a.help = true;
|
|
8061
|
+
else if (arg === "-v" || arg === "-V" || arg === "--version") a.version = true;
|
|
8008
8062
|
else if (arg.startsWith("--template=")) a.template = arg.slice(11);
|
|
8009
8063
|
else if (arg.startsWith("--theme=")) a.theme = arg.slice(8);
|
|
8010
8064
|
else if (arg.startsWith("--accent=")) a.theme = arg.slice(9);
|
|
@@ -8012,6 +8066,7 @@ function parseArgs(list) {
|
|
|
8012
8066
|
else if (arg.startsWith("--with=")) a.features.push(...splitList(arg.slice(7)));
|
|
8013
8067
|
else if (arg.startsWith("--features=")) a.features.push(...splitList(arg.slice(11)));
|
|
8014
8068
|
else if (!arg.startsWith("-") && !a.name) a.name = arg;
|
|
8069
|
+
else if (arg.startsWith("-")) a.unknown.push(arg);
|
|
8015
8070
|
}
|
|
8016
8071
|
return a;
|
|
8017
8072
|
}
|
|
@@ -8119,6 +8174,7 @@ ${c("bold", "Options")}
|
|
|
8119
8174
|
--no-git Skip git init
|
|
8120
8175
|
-y, --yes Accept defaults (needs <name>)
|
|
8121
8176
|
-h, --help Show this help
|
|
8177
|
+
-v, --version Print the version and exit
|
|
8122
8178
|
|
|
8123
8179
|
${c("bold", "Feature add-ons")} ${c("dim", "(--with) \u2014 free & keyless, local by default")}
|
|
8124
8180
|
${FEATURES.map((f) => ` ${f.key.padEnd(11)} ${f.description}`).join("\n")}
|
|
@@ -8592,6 +8648,16 @@ async function main() {
|
|
|
8592
8648
|
process$1.stdout.write(HELP + "\n");
|
|
8593
8649
|
return;
|
|
8594
8650
|
}
|
|
8651
|
+
if (args.version) {
|
|
8652
|
+
process$1.stdout.write(`create-lacspace-app ${cliVersion()}
|
|
8653
|
+
`);
|
|
8654
|
+
return;
|
|
8655
|
+
}
|
|
8656
|
+
if (args.unknown.length) {
|
|
8657
|
+
process$1.stdout.write(c("yellow", `
|
|
8658
|
+
! Unknown option${args.unknown.length > 1 ? "s" : ""}: ${args.unknown.join(", ")} \u2014 ignoring. Run with --help to see every flag.
|
|
8659
|
+
`));
|
|
8660
|
+
}
|
|
8595
8661
|
process$1.stdout.write(`
|
|
8596
8662
|
${c("bold", c("magenta", "\u25C6 create-lacspace-app"))} ${c("dim", "\u2014 a gorgeous Next.js starter, batteries wired")}
|
|
8597
8663
|
|
|
@@ -8790,8 +8856,18 @@ ${c("bold", "Your feature add-ons")} ${c("dim", "\u2014 details in LEARN.md")}
|
|
|
8790
8856
|
|
|
8791
8857
|
`);
|
|
8792
8858
|
}
|
|
8793
|
-
|
|
8794
|
-
|
|
8859
|
+
function isDirectRun() {
|
|
8860
|
+
const entry = process$1.argv[1];
|
|
8861
|
+
if (!entry) return false;
|
|
8862
|
+
const here = (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('lib.cjs', document.baseURI).href));
|
|
8863
|
+
if (here === url.pathToFileURL(entry).href) return true;
|
|
8864
|
+
try {
|
|
8865
|
+
if (here === url.pathToFileURL(fs.realpathSync(entry)).href) return true;
|
|
8866
|
+
} catch {
|
|
8867
|
+
}
|
|
8868
|
+
return path.basename(entry).replace(/\.[cm]?js$/, "") === "create-lacspace-app";
|
|
8869
|
+
}
|
|
8870
|
+
if (isDirectRun()) {
|
|
8795
8871
|
main().catch((err) => {
|
|
8796
8872
|
process$1.stdout.write(c("red", `
|
|
8797
8873
|
\u2717 ${err instanceof Error ? err.message : String(err)}
|
package/dist/lib.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { mkdir, writeFile } from 'fs/promises';
|
|
2
|
-
import {
|
|
3
|
-
import { existsSync, readdirSync, mkdirSync, writeFileSync } from 'fs';
|
|
2
|
+
import { basename, resolve, join, dirname } from 'path';
|
|
3
|
+
import { realpathSync, existsSync, readdirSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
4
4
|
import { createInterface } from 'readline/promises';
|
|
5
|
-
import {
|
|
5
|
+
import { stdout, exit, argv, stdin, cwd } from 'process';
|
|
6
6
|
import { spawnSync } from 'child_process';
|
|
7
|
-
import { pathToFileURL } from 'url';
|
|
7
|
+
import { pathToFileURL, fileURLToPath } from 'url';
|
|
8
8
|
|
|
9
9
|
// src/lib.ts
|
|
10
10
|
var C = {
|
|
@@ -7469,6 +7469,7 @@ var backendPkgJson = (ctx) => {
|
|
|
7469
7469
|
"@lacspace/id": "^1.1.0",
|
|
7470
7470
|
"@lacspace/rate-limit": "^1.2.0",
|
|
7471
7471
|
"@lacspace/cache": "^1.1.0",
|
|
7472
|
+
"@lacspace/logger": "^1.0.0",
|
|
7472
7473
|
...featureDeps,
|
|
7473
7474
|
[`${scope(ctx)}/types`]: "*"
|
|
7474
7475
|
},
|
|
@@ -7578,21 +7579,54 @@ var backendIndex = (ctx) => `import "./load-env.js"; // MUST be first: fills pro
|
|
|
7578
7579
|
import { env } from "./env.js";
|
|
7579
7580
|
import { connectDb } from "./db.js";
|
|
7580
7581
|
import { createApp } from "./app.js";
|
|
7582
|
+
import { log } from "./logger.js";
|
|
7581
7583
|
|
|
7582
7584
|
// how this works: connect to MongoDB, build the Express app, then listen.
|
|
7583
7585
|
async function main(): Promise<void> {
|
|
7584
7586
|
await connectDb(env.MONGODB_URI);
|
|
7585
7587
|
const app = createApp();
|
|
7586
7588
|
app.listen(env.PORT, () => {
|
|
7587
|
-
|
|
7589
|
+
log.info("${ctx.name} API ready", { url: \`http://localhost:\${env.PORT}\` });
|
|
7588
7590
|
});
|
|
7589
7591
|
}
|
|
7590
7592
|
|
|
7591
7593
|
main().catch((err) => {
|
|
7592
|
-
|
|
7594
|
+
log.fatal("Failed to start the API", { err });
|
|
7593
7595
|
process.exit(1);
|
|
7594
7596
|
});
|
|
7595
7597
|
`;
|
|
7598
|
+
var backendLogger = () => `import { createLogger, jsonConsole, prettyConsole } from "@lacspace/logger";
|
|
7599
|
+
import type { Request, Response, NextFunction } from "express";
|
|
7600
|
+
import { env } from "./env.js";
|
|
7601
|
+
|
|
7602
|
+
// how this works: one structured logger for the whole API (@lacspace/logger, zero-dep).
|
|
7603
|
+
// In production we emit JSON-per-line (clean for log aggregators); in dev we print a
|
|
7604
|
+
// readable, coloured line. Common secret fields are redacted before anything is logged.
|
|
7605
|
+
const isProd = env.NODE_ENV === "production";
|
|
7606
|
+
export const log = createLogger({
|
|
7607
|
+
level: isProd ? "info" : "debug",
|
|
7608
|
+
transports: [isProd ? jsonConsole() : prettyConsole({ colors: true })],
|
|
7609
|
+
redact: ["password", "token", "authorization", "*.password", "*.token"],
|
|
7610
|
+
});
|
|
7611
|
+
|
|
7612
|
+
// Request logger \u2014 one line per request with method, path, status and duration.
|
|
7613
|
+
// Each request gets a child logger with a short request id you can thread through.
|
|
7614
|
+
export function requestLogger(req: Request, res: Response, next: NextFunction): void {
|
|
7615
|
+
const start = Date.now();
|
|
7616
|
+
const reqId = Math.random().toString(36).slice(2, 10);
|
|
7617
|
+
(req as Request & { log: typeof log }).log = log.child({ reqId });
|
|
7618
|
+
res.on("finish", () => {
|
|
7619
|
+
log.info("request", {
|
|
7620
|
+
reqId,
|
|
7621
|
+
method: req.method,
|
|
7622
|
+
path: req.originalUrl,
|
|
7623
|
+
status: res.statusCode,
|
|
7624
|
+
ms: Date.now() - start,
|
|
7625
|
+
});
|
|
7626
|
+
});
|
|
7627
|
+
next();
|
|
7628
|
+
}
|
|
7629
|
+
`;
|
|
7596
7630
|
var backendDb = () => `import mongoose from "mongoose";
|
|
7597
7631
|
|
|
7598
7632
|
// how this works: opens the Mongoose connection. Call once at boot.
|
|
@@ -7908,16 +7942,19 @@ export default router;
|
|
|
7908
7942
|
var backendApp = (ctx) => `import express from "express";
|
|
7909
7943
|
import cors from "cors";
|
|
7910
7944
|
import { env } from "./env.js";
|
|
7945
|
+
import { requestLogger } from "./logger.js";
|
|
7911
7946
|
import { errorHandler } from "./middleware/error.js";
|
|
7912
7947
|
import { registerRoutes } from "./routes/index.js";
|
|
7913
7948
|
|
|
7914
7949
|
// how this works: assembles the Express app \u2014 CORS for the frontend, JSON parsing,
|
|
7915
|
-
// a health check, all route groups
|
|
7950
|
+
// structured request logging (@lacspace/logger), a health check, all route groups
|
|
7951
|
+
// (see routes/index.ts), then the error handler LAST.
|
|
7916
7952
|
export function createApp(): express.Express {
|
|
7917
7953
|
const app = express();
|
|
7918
7954
|
|
|
7919
7955
|
app.use(cors({ origin: env.CORS_ORIGIN.split(",").map((o) => o.trim()), credentials: true }));
|
|
7920
7956
|
app.use(express.json());
|
|
7957
|
+
app.use(requestLogger); // one structured log line per request
|
|
7921
7958
|
|
|
7922
7959
|
app.get("/health", (_req, res) => { res.json({ ok: true, service: "${ctx.name}-api" }); });
|
|
7923
7960
|
registerRoutes(app);
|
|
@@ -7958,6 +7995,7 @@ function backendFiles(ctx) {
|
|
|
7958
7995
|
"backend/src/env.ts": backendEnv(ctx),
|
|
7959
7996
|
"backend/src/db.ts": backendDb(),
|
|
7960
7997
|
"backend/src/cache.ts": backendCache(),
|
|
7998
|
+
"backend/src/logger.ts": backendLogger(),
|
|
7961
7999
|
"backend/src/app.ts": backendApp(ctx),
|
|
7962
8000
|
"backend/src/http.ts": backendHttp(),
|
|
7963
8001
|
"backend/src/express.d.ts": backendExpressTypes(),
|
|
@@ -7977,9 +8015,24 @@ function backendFiles(ctx) {
|
|
|
7977
8015
|
}
|
|
7978
8016
|
return files;
|
|
7979
8017
|
}
|
|
8018
|
+
function cliVersion() {
|
|
8019
|
+
try {
|
|
8020
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
8021
|
+
for (const rel of ["../package.json", "./package.json", "../../package.json"]) {
|
|
8022
|
+
const file = resolve(here, rel);
|
|
8023
|
+
if (!existsSync(file)) continue;
|
|
8024
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
8025
|
+
const version = parsed.version;
|
|
8026
|
+
const name = parsed.name;
|
|
8027
|
+
if (typeof version === "string" && (name === "create-lacspace-app" || rel !== "../../package.json")) return version;
|
|
8028
|
+
}
|
|
8029
|
+
} catch {
|
|
8030
|
+
}
|
|
8031
|
+
return "unknown";
|
|
8032
|
+
}
|
|
7980
8033
|
var splitList = (s) => s.split(",").map((x) => x.trim()).filter(Boolean);
|
|
7981
8034
|
function parseArgs(list) {
|
|
7982
|
-
const a = { features: [], yes: false, install: true, git: true, pm: "npm", help: false, dryRun: false };
|
|
8035
|
+
const a = { features: [], yes: false, install: true, git: true, pm: "npm", help: false, version: false, dryRun: false, unknown: [] };
|
|
7983
8036
|
for (let i = 0; i < list.length; i++) {
|
|
7984
8037
|
const arg = list[i];
|
|
7985
8038
|
const next = () => list[++i] ?? "";
|
|
@@ -8002,6 +8055,7 @@ function parseArgs(list) {
|
|
|
8002
8055
|
} else if (arg === "--recipe") a.recipe = next();
|
|
8003
8056
|
else if (arg.startsWith("--recipe=")) a.recipe = arg.slice(9);
|
|
8004
8057
|
else if (arg === "-h" || arg === "--help") a.help = true;
|
|
8058
|
+
else if (arg === "-v" || arg === "-V" || arg === "--version") a.version = true;
|
|
8005
8059
|
else if (arg.startsWith("--template=")) a.template = arg.slice(11);
|
|
8006
8060
|
else if (arg.startsWith("--theme=")) a.theme = arg.slice(8);
|
|
8007
8061
|
else if (arg.startsWith("--accent=")) a.theme = arg.slice(9);
|
|
@@ -8009,6 +8063,7 @@ function parseArgs(list) {
|
|
|
8009
8063
|
else if (arg.startsWith("--with=")) a.features.push(...splitList(arg.slice(7)));
|
|
8010
8064
|
else if (arg.startsWith("--features=")) a.features.push(...splitList(arg.slice(11)));
|
|
8011
8065
|
else if (!arg.startsWith("-") && !a.name) a.name = arg;
|
|
8066
|
+
else if (arg.startsWith("-")) a.unknown.push(arg);
|
|
8012
8067
|
}
|
|
8013
8068
|
return a;
|
|
8014
8069
|
}
|
|
@@ -8116,6 +8171,7 @@ ${c("bold", "Options")}
|
|
|
8116
8171
|
--no-git Skip git init
|
|
8117
8172
|
-y, --yes Accept defaults (needs <name>)
|
|
8118
8173
|
-h, --help Show this help
|
|
8174
|
+
-v, --version Print the version and exit
|
|
8119
8175
|
|
|
8120
8176
|
${c("bold", "Feature add-ons")} ${c("dim", "(--with) \u2014 free & keyless, local by default")}
|
|
8121
8177
|
${FEATURES.map((f) => ` ${f.key.padEnd(11)} ${f.description}`).join("\n")}
|
|
@@ -8589,6 +8645,16 @@ async function main() {
|
|
|
8589
8645
|
stdout.write(HELP + "\n");
|
|
8590
8646
|
return;
|
|
8591
8647
|
}
|
|
8648
|
+
if (args.version) {
|
|
8649
|
+
stdout.write(`create-lacspace-app ${cliVersion()}
|
|
8650
|
+
`);
|
|
8651
|
+
return;
|
|
8652
|
+
}
|
|
8653
|
+
if (args.unknown.length) {
|
|
8654
|
+
stdout.write(c("yellow", `
|
|
8655
|
+
! Unknown option${args.unknown.length > 1 ? "s" : ""}: ${args.unknown.join(", ")} \u2014 ignoring. Run with --help to see every flag.
|
|
8656
|
+
`));
|
|
8657
|
+
}
|
|
8592
8658
|
stdout.write(`
|
|
8593
8659
|
${c("bold", c("magenta", "\u25C6 create-lacspace-app"))} ${c("dim", "\u2014 a gorgeous Next.js starter, batteries wired")}
|
|
8594
8660
|
|
|
@@ -8787,8 +8853,18 @@ ${c("bold", "Your feature add-ons")} ${c("dim", "\u2014 details in LEARN.md")}
|
|
|
8787
8853
|
|
|
8788
8854
|
`);
|
|
8789
8855
|
}
|
|
8790
|
-
|
|
8791
|
-
|
|
8856
|
+
function isDirectRun() {
|
|
8857
|
+
const entry = argv[1];
|
|
8858
|
+
if (!entry) return false;
|
|
8859
|
+
const here = import.meta.url;
|
|
8860
|
+
if (here === pathToFileURL(entry).href) return true;
|
|
8861
|
+
try {
|
|
8862
|
+
if (here === pathToFileURL(realpathSync(entry)).href) return true;
|
|
8863
|
+
} catch {
|
|
8864
|
+
}
|
|
8865
|
+
return basename(entry).replace(/\.[cm]?js$/, "") === "create-lacspace-app";
|
|
8866
|
+
}
|
|
8867
|
+
if (isDirectRun()) {
|
|
8792
8868
|
main().catch((err) => {
|
|
8793
8869
|
stdout.write(c("red", `
|
|
8794
8870
|
\u2717 ${err instanceof Error ? err.message : String(err)}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-lacspace-app",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.1",
|
|
4
4
|
"description": "Scaffold a beautiful, production-ready Next.js app from a Lacspace template — portfolio, business, e-commerce, SaaS, blog, docs, dashboard, restaurant or marketplace — pre-wired with SEO, security headers, sitemap and robots. Use it as a CLI (like create-next-app, but you start gorgeous) or import it as a library to generate projects programmatically.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|