@lv00/new 0.0.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.
Files changed (88) hide show
  1. package/.config/ai/manifest.ts +20 -0
  2. package/.config/ai/rules/api.md +5 -0
  3. package/.config/ai/rules/db.md +7 -0
  4. package/.config/ai/rules/stack.md +9 -0
  5. package/.config/ai/rules/style.md +7 -0
  6. package/.config/ai/rules/svelte.md +7 -0
  7. package/.config/ai.rules.md +3 -0
  8. package/.config/dev.env.schema.ts +3 -0
  9. package/.config/standards.md +3 -0
  10. package/.scripts/generators/api-route.ts +36 -0
  11. package/.scripts/generators/job.ts +15 -0
  12. package/.scripts/generators/project.ts +184 -0
  13. package/.scripts/generators/script.ts +13 -0
  14. package/.scripts/manage.ts +95 -0
  15. package/.scripts/prompt.ts +40 -0
  16. package/README.md +43 -0
  17. package/bunfig.toml +5 -0
  18. package/package.json +27 -0
  19. package/packages/shared/package.json +8 -0
  20. package/packages/shared/src/config/index.ts +1 -0
  21. package/packages/shared/src/env/index.ts +1 -0
  22. package/packages/shared/src/errors/index.ts +1 -0
  23. package/packages/shared/src/index.ts +4 -0
  24. package/packages/shared/src/utils/index.ts +1 -0
  25. package/packages/ui/package.json +6 -0
  26. package/packages/ui/src/index.ts +1 -0
  27. package/projects/caca/api/package.json +6 -0
  28. package/projects/caca/api/src/app.ts +6 -0
  29. package/projects/caca/api/src/index.ts +3 -0
  30. package/projects/caca/api/src/plugins/.gitkeep +0 -0
  31. package/projects/caca/api/src/routes/health.ts +3 -0
  32. package/projects/caca/api/src/schemas/.gitkeep +0 -0
  33. package/projects/caca/infra/caddy/.gitkeep +0 -0
  34. package/projects/caca/infra/docker/.gitkeep +0 -0
  35. package/projects/caca/jobs/src/caca.job.ts +4 -0
  36. package/projects/caca/project.config.ts +6 -0
  37. package/projects/caca/scripts/caca.script.ts +2 -0
  38. package/projects/caca/web/package.json +16 -0
  39. package/projects/caca/web/src/App.svelte +5 -0
  40. package/projects/caca/web/src/app.html +12 -0
  41. package/projects/caca/web/src/main.ts +4 -0
  42. package/projects/caca/web/svelte.config.js +5 -0
  43. package/projects/example/api/package.json +6 -0
  44. package/projects/example/api/src/app.ts +6 -0
  45. package/projects/example/api/src/index.ts +3 -0
  46. package/projects/example/api/src/plugins/.gitkeep +0 -0
  47. package/projects/example/api/src/routes/health.ts +3 -0
  48. package/projects/example/api/src/schemas/.gitkeep +0 -0
  49. package/projects/example/jobs/src/example.job.ts +4 -0
  50. package/projects/example/project.config.ts +6 -0
  51. package/projects/example/scripts/example.script.ts +2 -0
  52. package/projects/example/web/package.json +16 -0
  53. package/projects/example/web/src/App.svelte +5 -0
  54. package/projects/example/web/src/app.html +12 -0
  55. package/projects/example/web/src/main.ts +4 -0
  56. package/projects/example/web/svelte.config.js +5 -0
  57. package/projects/myapp/api/package.json +6 -0
  58. package/projects/myapp/api/src/app.ts +6 -0
  59. package/projects/myapp/api/src/index.ts +3 -0
  60. package/projects/myapp/api/src/plugins/.gitkeep +0 -0
  61. package/projects/myapp/api/src/routes/health.ts +3 -0
  62. package/projects/myapp/api/src/schemas/.gitkeep +0 -0
  63. package/projects/myapp/infra/caddy/.gitkeep +0 -0
  64. package/projects/myapp/infra/docker/.gitkeep +0 -0
  65. package/projects/myapp/jobs/src/myapp.job.ts +4 -0
  66. package/projects/myapp/project.config.ts +6 -0
  67. package/projects/myapp/scripts/myapp.script.ts +2 -0
  68. package/projects/myapp/web/package.json +16 -0
  69. package/projects/myapp/web/src/App.svelte +5 -0
  70. package/projects/myapp/web/src/app.html +12 -0
  71. package/projects/myapp/web/src/main.ts +4 -0
  72. package/projects/myapp/web/svelte.config.js +5 -0
  73. package/projects/test/api/package.json +6 -0
  74. package/projects/test/api/src/app.ts +6 -0
  75. package/projects/test/api/src/index.ts +3 -0
  76. package/projects/test/api/src/plugins/.gitkeep +0 -0
  77. package/projects/test/api/src/routes/health.ts +3 -0
  78. package/projects/test/api/src/schemas/.gitkeep +0 -0
  79. package/projects/test/infra/caddy/.gitkeep +0 -0
  80. package/projects/test/infra/docker/.gitkeep +0 -0
  81. package/projects/test/jobs/src/test.job.ts +4 -0
  82. package/projects/test/project.config.ts +6 -0
  83. package/projects/test/scripts/test.script.ts +2 -0
  84. package/projects/test/web/package.json +16 -0
  85. package/projects/test/web/src/App.svelte +5 -0
  86. package/projects/test/web/src/app.html +12 -0
  87. package/projects/test/web/src/main.ts +4 -0
  88. package/projects/test/web/svelte.config.js +5 -0
@@ -0,0 +1,20 @@
1
+ export const AI_RULES: { id: string; label: string; hint?: string }[] = [
2
+ { id: "stack", label: "Stack (Bun, Svelte 5, Drizzle, ElysiaJS)", hint: "recommandé" },
3
+ { id: "style", label: "Style (minimal, clarté)" },
4
+ { id: "db", label: "Base de données (Drizzle, SQLite)" },
5
+ { id: "svelte", label: "Svelte 5 (runes)" },
6
+ { id: "api", label: "API (ElysiaJS)" },
7
+ ];
8
+
9
+ export const AI_EDITORS = [
10
+ { value: "cursor" as const, label: "Cursor", hint: ".cursor/rules/" },
11
+ { value: "codex" as const, label: "Codex", hint: ".codex/rules/" },
12
+ { value: "none" as const, label: "Aucun" },
13
+ ] as const;
14
+
15
+ export type AiEditor = (typeof AI_EDITORS)[number]["value"];
16
+
17
+ export const EDITOR_RULES_DIR: Record<Exclude<AiEditor, "none">, string> = {
18
+ cursor: ".cursor/rules",
19
+ codex: ".codex/rules",
20
+ };
@@ -0,0 +1,5 @@
1
+ # API
2
+
3
+ - ElysiaJS pour les routes
4
+ - Réponses JSON explicites
5
+ - Pas de logique métier dans les handlers (déléguer aux services)
@@ -0,0 +1,7 @@
1
+ # Base de données
2
+
3
+ - SQLite d’abord
4
+ - Drizzle schema-first
5
+ - Migrations automatisées avec drizzle-kit (pas d'écriture manuelle de migrations)
6
+ - Pas de logique métier dans la DB
7
+ - Pas d’ORM dynamique
@@ -0,0 +1,9 @@
1
+ # Stack
2
+
3
+ - Runtime : Bun uniquement
4
+ - Frontend : Svelte 5 (runes, syntaxe moderne)
5
+ - Backend : Bun natif avec ElysiaJS (pas Node fallback)
6
+ - ORM : Drizzle ORM exclusivement (drizzle-kit pour les migrations)
7
+ - Base : SQLite par défaut
8
+
9
+ Interdictions : Node.js, npm, yarn, pnpm ; Svelte ≤4 ; Prisma, TypeORM ; PostgreSQL/MySQL/MongoDB sauf instruction explicite.
@@ -0,0 +1,7 @@
1
+ # Style
2
+
3
+ - Minimal, lisible, déterministe
4
+ - Pas de sur-ingénierie ni patterns académiques
5
+ - Pas de commentaires évidents
6
+ - Préférer la clarté à la flexibilité
7
+ - Refuser toute solution « au cas où » et toute dette cognitive
@@ -0,0 +1,7 @@
1
+ # Svelte 5
2
+
3
+ - Runes uniquement : $state, $derived, $effect
4
+ - Pas de stores legacy
5
+ - Composants petits et fermés
6
+ - Pas de props mutables implicites
7
+ - Pas de logique métier dans le markup
@@ -0,0 +1,3 @@
1
+ # Règles IA
2
+
3
+ Règles et conventions pour l’assistant IA sur ce repo.
@@ -0,0 +1,3 @@
1
+ export type DevEnv = {
2
+ NODE_ENV: "development" | "production" | "test";
3
+ };
@@ -0,0 +1,3 @@
1
+ # Standards
2
+
3
+ Conventions de code et d’architecture du monorepo.
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env bun
2
+ import { join } from "node:path";
3
+ import { readFile, writeFile } from "node:fs/promises";
4
+
5
+ function toCamel(s: string) {
6
+ return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
7
+ }
8
+
9
+ export async function generateApiRoute(templateRoot: string, projectName: string, name: string) {
10
+ const routePath = join(templateRoot, "projects", projectName, "api", "src", "routes", `${name}.ts`);
11
+ const appPath = join(templateRoot, "projects", projectName, "api", "src", "app.ts");
12
+
13
+ const routeVar = toCamel(name);
14
+ await writeFile(
15
+ routePath,
16
+ `import { Hono } from "hono";
17
+
18
+ export const ${routeVar} = new Hono().get("/", (c) => c.json({ ${routeVar}: true }));
19
+ `
20
+ );
21
+
22
+ const appContent = await readFile(appPath, "utf-8");
23
+ if (appContent.includes(`from "./routes/${name}"`)) return;
24
+
25
+ const newImport = `import { ${routeVar} } from "./routes/${name}";`;
26
+ const lastRouteImport = appContent.match(/import \{ \w+ \} from "\.\/routes\/[^"]+";/g)?.pop();
27
+ const withImport = lastRouteImport
28
+ ? appContent.replace(lastRouteImport, `${lastRouteImport}\n${newImport}`)
29
+ : appContent.replace("import { Hono } from \"hono\";", `import { Hono } from "hono";\n${newImport}`);
30
+
31
+ const lastAppRoute = withImport.match(/app\.route\("[^"]+", \w+\);/g)?.pop();
32
+ const withRoute = lastAppRoute
33
+ ? withImport.replace(lastAppRoute, `${lastAppRoute}\napp.route("/${name}", ${routeVar});`)
34
+ : withImport.replace("const app = new Hono();", `const app = new Hono();\napp.route("/${name}", ${routeVar});`);
35
+ await writeFile(appPath, withRoute);
36
+ }
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env bun
2
+ import { join } from "node:path";
3
+ import { writeFile } from "node:fs/promises";
4
+
5
+ export async function generateJob(templateRoot: string, projectName: string, name: string) {
6
+ const jobPath = join(templateRoot, "projects", projectName, "jobs", "src", `${name}.job.ts`);
7
+ await writeFile(
8
+ jobPath,
9
+ `#!/usr/bin/env bun
10
+ export async function run() {
11
+ console.log("${name} job");
12
+ }
13
+ `
14
+ );
15
+ }
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/env bun
2
+ import { join } from "node:path";
3
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
4
+ import { EDITOR_RULES_DIR } from "../../.config/ai/manifest";
5
+
6
+ const EXAMPLE = "example";
7
+
8
+ type AiConfig = { type: "cursor" | "codex"; ruleIds: string[] };
9
+
10
+ export async function generateProject(
11
+ templateRoot: string,
12
+ name: string,
13
+ destinationRoot: string,
14
+ options?: { editor?: AiConfig }
15
+ ) {
16
+ const targetDir = join(destinationRoot, name);
17
+
18
+ if (await Bun.file(join(targetDir, "project.config.ts")).exists()) {
19
+ throw new Error(`Le projet ${name} existe déjà.`);
20
+ }
21
+
22
+ const repl = (s: string) => s.replaceAll(EXAMPLE, name).replaceAll("example", name);
23
+
24
+ await mkdir(join(targetDir, "api", "src", "routes"), { recursive: true });
25
+ await mkdir(join(targetDir, "api", "src", "plugins"), { recursive: true });
26
+ await mkdir(join(targetDir, "api", "src", "schemas"), { recursive: true });
27
+ await mkdir(join(targetDir, "web", "src"), { recursive: true });
28
+ await mkdir(join(targetDir, "jobs", "src"), { recursive: true });
29
+ await mkdir(join(targetDir, "scripts"), { recursive: true });
30
+ await mkdir(join(targetDir, "infra", "docker"), { recursive: true });
31
+ await mkdir(join(targetDir, "infra", "caddy"), { recursive: true });
32
+
33
+ await writeFile(
34
+ join(targetDir, "project.config.ts"),
35
+ repl(
36
+ `export default {
37
+ name: "${name}",
38
+ org: "groundZero",
39
+ ports: { api: 3001, web: 3000 },
40
+ env: ["DATABASE_URL", "JWT_SECRET"]
41
+ }
42
+ `
43
+ )
44
+ );
45
+
46
+ await writeFile(
47
+ join(targetDir, "api", "package.json"),
48
+ JSON.stringify({ name: `${name}-api`, version: "0.0.0", private: true, type: "module" }, null, 2)
49
+ );
50
+ await writeFile(
51
+ join(targetDir, "api", "bunfig.toml"),
52
+ `[run]
53
+ preload = ["./src/index.ts"]
54
+ `
55
+ );
56
+ await writeFile(
57
+ join(targetDir, "api", "src", "index.ts"),
58
+ `import { app } from "./app";
59
+
60
+ export default app;
61
+ `
62
+ );
63
+ await writeFile(
64
+ join(targetDir, "api", "src", "app.ts"),
65
+ repl(
66
+ `import { Hono } from "hono";
67
+ import { health } from "./routes/health";
68
+
69
+ const app = new Hono();
70
+ app.route("/health", health);
71
+ export { app };
72
+ `
73
+ )
74
+ );
75
+ await writeFile(
76
+ join(targetDir, "api", "src", "routes", "health.ts"),
77
+ `import { Hono } from "hono";
78
+
79
+ export const health = new Hono().get("/", (c) => c.json({ ok: true }));
80
+ `
81
+ );
82
+ await writeFile(join(targetDir, "api", "src", "plugins", ".gitkeep"), "");
83
+ await writeFile(join(targetDir, "api", "src", "schemas", ".gitkeep"), "");
84
+
85
+ await writeFile(
86
+ join(targetDir, "web", "package.json"),
87
+ JSON.stringify(
88
+ {
89
+ name: `${name}-web`,
90
+ version: "0.0.0",
91
+ private: true,
92
+ type: "module",
93
+ scripts: { dev: "vite", build: "vite build", preview: "vite preview" },
94
+ devDependencies: {
95
+ "@sveltejs/vite-plugin-svelte": "^4.0.0",
96
+ svelte: "^5.0.0",
97
+ vite: "^6.0.0",
98
+ },
99
+ },
100
+ null,
101
+ 2
102
+ )
103
+ );
104
+ await writeFile(
105
+ join(targetDir, "web", "svelte.config.js"),
106
+ `import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
107
+
108
+ export default {
109
+ preprocess: vitePreprocess(),
110
+ };
111
+ `
112
+ );
113
+ await writeFile(
114
+ join(targetDir, "web", "src", "app.html"),
115
+ repl(
116
+ `<!DOCTYPE html>
117
+ <html lang="fr">
118
+ <head>
119
+ <meta charset="utf-8" />
120
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
121
+ <title>${name}</title>
122
+ </head>
123
+ <body>
124
+ <div id="app"></div>
125
+ <script type="module" src="./main.ts"></script>
126
+ </body>
127
+ </html>
128
+ `
129
+ )
130
+ );
131
+ await writeFile(
132
+ join(targetDir, "web", "src", "main.ts"),
133
+ `import App from "./App.svelte";
134
+
135
+ const app = new App({ target: document.getElementById("app")! });
136
+ export default app;
137
+ `
138
+ );
139
+ await writeFile(
140
+ join(targetDir, "web", "src", "App.svelte"),
141
+ `<script lang="ts">
142
+ let count = $state(0);
143
+ </script>
144
+
145
+ <button onclick={() => count++}>clicks: {count}</button>
146
+ `
147
+ );
148
+
149
+ await writeFile(
150
+ join(targetDir, "jobs", "bunfig.toml"),
151
+ `[run]
152
+ preload = []
153
+ `
154
+ );
155
+ await writeFile(
156
+ join(targetDir, "jobs", "src", `${name}.job.ts`),
157
+ `#!/usr/bin/env bun
158
+ export async function run() {
159
+ console.log("${name} job");
160
+ }
161
+ `
162
+ );
163
+
164
+ await writeFile(
165
+ join(targetDir, "scripts", `${name}.script.ts`),
166
+ `#!/usr/bin/env bun
167
+ console.log("${name} script");
168
+ `
169
+ );
170
+
171
+ await writeFile(join(targetDir, "infra", "docker", ".gitkeep"), "");
172
+ await writeFile(join(targetDir, "infra", "caddy", ".gitkeep"), "");
173
+
174
+ if (options?.editor && options.editor.ruleIds.length > 0) {
175
+ const rulesDir = join(targetDir, EDITOR_RULES_DIR[options.editor.type]);
176
+ await mkdir(rulesDir, { recursive: true });
177
+ const templateRulesDir = join(templateRoot, ".config", "ai", "rules");
178
+ for (const id of options.editor.ruleIds) {
179
+ const src = join(templateRulesDir, `${id}.md`);
180
+ const content = await readFile(src, "utf-8").catch(() => null);
181
+ if (content) await writeFile(join(rulesDir, `${id}.md`), content);
182
+ }
183
+ }
184
+ }
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env bun
2
+ import { join } from "node:path";
3
+ import { writeFile } from "node:fs/promises";
4
+
5
+ export async function generateScript(templateRoot: string, projectName: string, name: string) {
6
+ const scriptPath = join(templateRoot, "projects", projectName, "scripts", `${name}.script.ts`);
7
+ await writeFile(
8
+ scriptPath,
9
+ `#!/usr/bin/env bun
10
+ console.log("${name} script");
11
+ `
12
+ );
13
+ }
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env bun
2
+ import { join, resolve } from "node:path";
3
+ import { existsSync } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ import * as p from "@clack/prompts";
6
+ import { ask, choose, multiselect, say } from "./prompt";
7
+ import { generateProject } from "./generators/project";
8
+ import { AI_EDITORS, AI_RULES, EDITOR_RULES_DIR } from "../.config/ai/manifest";
9
+ import { generateApiRoute } from "./generators/api-route";
10
+ import { generateJob } from "./generators/job";
11
+ import { generateScript } from "./generators/script";
12
+
13
+ const templateRoot = existsSync(join(process.cwd(), ".scripts", "manage.ts"))
14
+ ? process.cwd()
15
+ : join(import.meta.dir, "..");
16
+
17
+ const ACTIONS = [
18
+ { value: "project" as const, label: "Nouveau projet" },
19
+ { value: "api-route" as const, label: "Nouvelle route API" },
20
+ { value: "job" as const, label: "Nouveau job" },
21
+ { value: "script" as const, label: "Nouveau script" },
22
+ { value: "exit" as const, label: "Quitter" },
23
+ ] as const satisfies { value: string; label: string }[];
24
+
25
+ async function main() {
26
+ p.intro("manage");
27
+
28
+ const action = await choose("Que veux-tu faire ?", ACTIONS);
29
+ if (action === "exit") {
30
+ p.outro("À bientôt.");
31
+ process.exit(0);
32
+ }
33
+
34
+ if (action === "project") {
35
+ const name = await ask("Nom du projet ?");
36
+ const destRaw = await ask("Où créer le projet ?", {
37
+ placeholder: process.cwd(),
38
+ validate: (v) => undefined,
39
+ });
40
+ const destExpanded = destRaw.trim().startsWith("~")
41
+ ? join(homedir(), destRaw.trim().slice(1))
42
+ : destRaw.trim();
43
+ const destinationRoot = destExpanded === "" ? process.cwd() : resolve(process.cwd(), destExpanded);
44
+
45
+ const editor = await choose("Editor IA pour les règles ?", [...AI_EDITORS]);
46
+ let ruleIds: string[] = [];
47
+ if (editor !== "none") {
48
+ const selected = await multiselect(
49
+ "Quelles règles inclure ?",
50
+ AI_RULES.map((r) => ({ value: r.id, label: r.label, ...(r.hint && { hint: r.hint }) }))
51
+ );
52
+ ruleIds = selected;
53
+ }
54
+
55
+ await generateProject(templateRoot, name, destinationRoot, {
56
+ editor: editor === "none" ? undefined : { type: editor, ruleIds },
57
+ });
58
+ say(`Projet ${name} créé dans ${join(destinationRoot, name)}`, "ok");
59
+ if (editor !== "none" && ruleIds.length > 0) {
60
+ say(`${ruleIds.length} règle(s) copiée(s) dans ${EDITOR_RULES_DIR[editor]}`, "ok");
61
+ }
62
+ p.outro("Done.");
63
+ return;
64
+ }
65
+
66
+ const projectName = await ask("Nom du projet (ex. example) ?");
67
+
68
+ switch (action) {
69
+ case "api-route": {
70
+ const name = await ask("Nom de la route (ex. users) ?");
71
+ await generateApiRoute(templateRoot, projectName, name);
72
+ say(`Route ${name} ajoutée dans projects/${projectName}/api`, "ok");
73
+ break;
74
+ }
75
+ case "job": {
76
+ const name = await ask("Nom du job (ex. sync) ?");
77
+ await generateJob(templateRoot, projectName, name);
78
+ say(`Job ${name} créé dans projects/${projectName}/jobs`, "ok");
79
+ break;
80
+ }
81
+ case "script": {
82
+ const name = await ask("Nom du script (ex. seed) ?");
83
+ await generateScript(templateRoot, projectName, name);
84
+ say(`Script ${name} créé dans projects/${projectName}/scripts`, "ok");
85
+ break;
86
+ }
87
+ }
88
+
89
+ p.outro("Done.");
90
+ }
91
+
92
+ main().catch((e) => {
93
+ p.log.error(e instanceof Error ? e.message : String(e));
94
+ process.exit(1);
95
+ });
@@ -0,0 +1,40 @@
1
+ import * as p from "@clack/prompts";
2
+
3
+ function orExit<T>(value: T | symbol): T {
4
+ if (p.isCancel(value)) {
5
+ p.cancel("Annulé.");
6
+ process.exit(0);
7
+ }
8
+ return value as T;
9
+ }
10
+
11
+ export function ask(message: string, opts?: { placeholder?: string; validate?: (v: string) => string | void }): Promise<string> {
12
+ const v = p.text({
13
+ message,
14
+ placeholder: opts?.placeholder,
15
+ validate: opts?.validate ?? ((v) => (v.trim() ? undefined : "Requis.")),
16
+ });
17
+ return Promise.resolve(orExit(v));
18
+ }
19
+
20
+ export function choose<T>(message: string, options: { value: T; label: string; hint?: string }[]): Promise<T> {
21
+ const v = p.select({
22
+ message,
23
+ options: options.map((o) => ({ value: o.value, label: o.label, hint: o.hint })),
24
+ });
25
+ return Promise.resolve(orExit(v));
26
+ }
27
+
28
+ export function multiselect<T>(message: string, options: { value: T; label: string; hint?: string }[]): Promise<T[]> {
29
+ const v = p.multiselect({
30
+ message,
31
+ options: options.map((o) => ({ value: o.value, label: o.label, hint: o.hint })),
32
+ required: false,
33
+ });
34
+ return Promise.resolve(orExit(v) as T[]);
35
+ }
36
+
37
+ export function say(msg: string, style: "ok" | "dim" = "dim") {
38
+ if (style === "ok") p.log.success(msg);
39
+ else p.log.info(msg);
40
+ }
package/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # Template
2
+
3
+ Monorepo Bun · Svelte 5 · Drizzle · SQLite.
4
+
5
+ ## Installation globale (sur ta machine)
6
+
7
+ Depuis la racine du repo :
8
+
9
+ ```bash
10
+ bun link
11
+ ```
12
+
13
+ La commande `manage` est alors disponible partout. Pour la retirer : `bun unlink template`.
14
+
15
+ ## Installation via package publié (plus tard)
16
+
17
+ Après publication sur npm (ou autre registry) :
18
+
19
+ ```bash
20
+ bunx template
21
+ # ou
22
+ bun install -g template
23
+ manage
24
+ ```
25
+
26
+ Avant de publier : enlève `"private": true` si présent, choisis un nom dispo (ex. `@ton-user/template`), incrémente `version`, puis `npm publish` ou `bun publish`.
27
+
28
+ ## Structure
29
+
30
+ - `.config/` — règles IA, schéma env, standards
31
+ - `.scripts/` — manage.ts et générateurs
32
+ - `packages/shared` — config, env, errors, utils
33
+ - `packages/ui` — composants partagés
34
+ - `projects/<name>/` — api (Hono), web (Svelte), jobs, scripts, infra
35
+
36
+ ## Usage (dans le repo)
37
+
38
+ ```bash
39
+ bun install
40
+ bun run manage
41
+ # ou après bun link :
42
+ manage
43
+ ```
package/bunfig.toml ADDED
@@ -0,0 +1,5 @@
1
+ [install]
2
+ peer = false
3
+
4
+ [run]
5
+ bun = true
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@lv00/new",
3
+ "version": "0.0.0",
4
+ "description": "CLI monorepo Bun · Svelte 5 · Drizzle",
5
+ "type": "module",
6
+ "bin": {
7
+ "manage": ".scripts/manage.ts"
8
+ },
9
+ "workspaces": ["packages/*"],
10
+ "files": [
11
+ ".config",
12
+ ".scripts",
13
+ "packages",
14
+ "projects",
15
+ "bunfig.toml"
16
+ ],
17
+ "scripts": {
18
+ "manage": "bun run .scripts/manage.ts"
19
+ },
20
+ "dependencies": {
21
+ "@clack/prompts": "^0.8.0"
22
+ },
23
+ "devDependencies": {
24
+ "bun-types": "latest",
25
+ "@types/node": "latest"
26
+ }
27
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "@repo/shared",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "./src/index.ts",
7
+ "exports": "./src/index.ts"
8
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ export * from "./config";
2
+ export * from "./env";
3
+ export * from "./errors";
4
+ export * from "./utils";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "@repo/ui",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module"
6
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "caca-api",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module"
6
+ }
@@ -0,0 +1,6 @@
1
+ import { Hono } from "hono";
2
+ import { health } from "./routes/health";
3
+
4
+ const app = new Hono();
5
+ app.route("/health", health);
6
+ export { app };
@@ -0,0 +1,3 @@
1
+ import { app } from "./app";
2
+
3
+ export default app;
File without changes
@@ -0,0 +1,3 @@
1
+ import { Hono } from "hono";
2
+
3
+ export const health = new Hono().get("/", (c) => c.json({ ok: true }));
File without changes
File without changes
File without changes
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bun
2
+ export async function run() {
3
+ console.log("caca job");
4
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ name: "caca",
3
+ org: "groundZero",
4
+ ports: { api: 3001, web: 3000 },
5
+ env: ["DATABASE_URL", "JWT_SECRET"]
6
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bun
2
+ console.log("caca script");
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "caca-web",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "devDependencies": {
12
+ "@sveltejs/vite-plugin-svelte": "^4.0.0",
13
+ "svelte": "^5.0.0",
14
+ "vite": "^6.0.0"
15
+ }
16
+ }
@@ -0,0 +1,5 @@
1
+ <script lang="ts">
2
+ let count = $state(0);
3
+ </script>
4
+
5
+ <button onclick={() => count++}>clicks: {count}</button>
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>caca</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="./main.ts"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,4 @@
1
+ import App from "./App.svelte";
2
+
3
+ const app = new App({ target: document.getElementById("app")! });
4
+ export default app;
@@ -0,0 +1,5 @@
1
+ import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
2
+
3
+ export default {
4
+ preprocess: vitePreprocess(),
5
+ };
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "example-api",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module"
6
+ }
@@ -0,0 +1,6 @@
1
+ import { Hono } from "hono";
2
+ import { health } from "./routes/health";
3
+
4
+ const app = new Hono();
5
+ app.route("/health", health);
6
+ export { app };
@@ -0,0 +1,3 @@
1
+ import { app } from "./app";
2
+
3
+ export default app;
File without changes
@@ -0,0 +1,3 @@
1
+ import { Hono } from "hono";
2
+
3
+ export const health = new Hono().get("/", (c) => c.json({ ok: true }));
File without changes
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bun
2
+ export async function run() {
3
+ console.log("example job");
4
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ name: "example",
3
+ org: "groundZero",
4
+ ports: { api: 3001, web: 3000 },
5
+ env: ["DATABASE_URL", "JWT_SECRET"]
6
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bun
2
+ console.log("example script");
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "example-web",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "devDependencies": {
12
+ "@sveltejs/vite-plugin-svelte": "^4.0.0",
13
+ "svelte": "^5.0.0",
14
+ "vite": "^6.0.0"
15
+ }
16
+ }
@@ -0,0 +1,5 @@
1
+ <script lang="ts">
2
+ let count = $state(0);
3
+ </script>
4
+
5
+ <button onclick={() => count++}>clicks: {count}</button>
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>example</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="./main.ts"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,4 @@
1
+ import App from "./App.svelte";
2
+
3
+ const app = new App({ target: document.getElementById("app")! });
4
+ export default app;
@@ -0,0 +1,5 @@
1
+ import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
2
+
3
+ export default {
4
+ preprocess: vitePreprocess(),
5
+ };
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "myapp-api",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module"
6
+ }
@@ -0,0 +1,6 @@
1
+ import { Hono } from "hono";
2
+ import { health } from "./routes/health";
3
+
4
+ const app = new Hono();
5
+ app.route("/health", health);
6
+ export { app };
@@ -0,0 +1,3 @@
1
+ import { app } from "./app";
2
+
3
+ export default app;
File without changes
@@ -0,0 +1,3 @@
1
+ import { Hono } from "hono";
2
+
3
+ export const health = new Hono().get("/", (c) => c.json({ ok: true }));
File without changes
File without changes
File without changes
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bun
2
+ export async function run() {
3
+ console.log("myapp job");
4
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ name: "myapp",
3
+ org: "groundZero",
4
+ ports: { api: 3001, web: 3000 },
5
+ env: ["DATABASE_URL", "JWT_SECRET"]
6
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bun
2
+ console.log("myapp script");
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "myapp-web",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "devDependencies": {
12
+ "@sveltejs/vite-plugin-svelte": "^4.0.0",
13
+ "svelte": "^5.0.0",
14
+ "vite": "^6.0.0"
15
+ }
16
+ }
@@ -0,0 +1,5 @@
1
+ <script lang="ts">
2
+ let count = $state(0);
3
+ </script>
4
+
5
+ <button onclick={() => count++}>clicks: {count}</button>
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>myapp</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="./main.ts"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,4 @@
1
+ import App from "./App.svelte";
2
+
3
+ const app = new App({ target: document.getElementById("app")! });
4
+ export default app;
@@ -0,0 +1,5 @@
1
+ import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
2
+
3
+ export default {
4
+ preprocess: vitePreprocess(),
5
+ };
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "test-api",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module"
6
+ }
@@ -0,0 +1,6 @@
1
+ import { Hono } from "hono";
2
+ import { health } from "./routes/health";
3
+
4
+ const app = new Hono();
5
+ app.route("/health", health);
6
+ export { app };
@@ -0,0 +1,3 @@
1
+ import { app } from "./app";
2
+
3
+ export default app;
File without changes
@@ -0,0 +1,3 @@
1
+ import { Hono } from "hono";
2
+
3
+ export const health = new Hono().get("/", (c) => c.json({ ok: true }));
File without changes
File without changes
File without changes
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bun
2
+ export async function run() {
3
+ console.log("test job");
4
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ name: "test",
3
+ org: "groundZero",
4
+ ports: { api: 3001, web: 3000 },
5
+ env: ["DATABASE_URL", "JWT_SECRET"]
6
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bun
2
+ console.log("test script");
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "test-web",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "devDependencies": {
12
+ "@sveltejs/vite-plugin-svelte": "^4.0.0",
13
+ "svelte": "^5.0.0",
14
+ "vite": "^6.0.0"
15
+ }
16
+ }
@@ -0,0 +1,5 @@
1
+ <script lang="ts">
2
+ let count = $state(0);
3
+ </script>
4
+
5
+ <button onclick={() => count++}>clicks: {count}</button>
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>test</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="./main.ts"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,4 @@
1
+ import App from "./App.svelte";
2
+
3
+ const app = new App({ target: document.getElementById("app")! });
4
+ export default app;
@@ -0,0 +1,5 @@
1
+ import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
2
+
3
+ export default {
4
+ preprocess: vitePreprocess(),
5
+ };