@junejs/cli 0.0.50 → 0.0.51-dev.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/dist/cli.d.ts +10 -0
- package/dist/cli.js +170 -0
- package/dist/messages-DfAlYpfN.js +26 -0
- package/dist/watch-CeyzNVKv.js +97 -0
- package/package.json +13 -6
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/cli.d.ts
|
|
2
|
+
type Parsed = {
|
|
3
|
+
verb: string;
|
|
4
|
+
positional: string[];
|
|
5
|
+
flags: Record<string, string | boolean>;
|
|
6
|
+
};
|
|
7
|
+
declare function parse(argv: string[]): Parsed;
|
|
8
|
+
declare function run(argv: string[]): Promise<number | undefined>;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { Parsed, parse, run };
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
//#region src/cli.ts
|
|
5
|
+
function parse(argv) {
|
|
6
|
+
const [verb = "", ...rest] = argv;
|
|
7
|
+
const positional = [];
|
|
8
|
+
const flags = {};
|
|
9
|
+
for (let i = 0; i < rest.length; i++) {
|
|
10
|
+
const a = rest[i];
|
|
11
|
+
if (a.startsWith("--")) {
|
|
12
|
+
const key = a.slice(2);
|
|
13
|
+
const next = rest[i + 1];
|
|
14
|
+
if (next !== void 0 && !next.startsWith("--")) {
|
|
15
|
+
flags[key] = next;
|
|
16
|
+
i++;
|
|
17
|
+
} else flags[key] = true;
|
|
18
|
+
} else positional.push(a);
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
verb,
|
|
22
|
+
positional,
|
|
23
|
+
flags
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const HELP = `june — the agent-native React framework
|
|
27
|
+
|
|
28
|
+
Usage: june <command> [dir] [options]
|
|
29
|
+
|
|
30
|
+
Commands:
|
|
31
|
+
dev Start the dev server --port <n> --no-watch
|
|
32
|
+
build Build a workerd-ready bundle --out <dir>
|
|
33
|
+
deploy Build + deploy (workers/vercel) --dry-run --prod --skip-migrate --allow-destructive
|
|
34
|
+
gen Freeze content/schema --check
|
|
35
|
+
db Database tasks (db migrate, db types) --allow-destructive
|
|
36
|
+
info Show routes + the agent surface
|
|
37
|
+
help Show this help
|
|
38
|
+
|
|
39
|
+
The app directory defaults to the current directory.
|
|
40
|
+
`;
|
|
41
|
+
function appRoot(positional) {
|
|
42
|
+
return positional[0] ? resolve(positional[0]) : process.cwd();
|
|
43
|
+
}
|
|
44
|
+
async function info(root) {
|
|
45
|
+
const { createApp, loadJuneConfig } = await import("@junejs/server");
|
|
46
|
+
const { resolveAgent } = await import("@junejs/core/config");
|
|
47
|
+
const { ACTION_REGISTRY } = await import("@junejs/core/agent");
|
|
48
|
+
const config = await loadJuneConfig(root);
|
|
49
|
+
const app = createApp({
|
|
50
|
+
appDir: join(root, "app"),
|
|
51
|
+
config
|
|
52
|
+
});
|
|
53
|
+
await app.warmup();
|
|
54
|
+
const routes = await app.routePaths();
|
|
55
|
+
const agent = resolveAgent(config.agent);
|
|
56
|
+
const tools = [...ACTION_REGISTRY.values()].filter((a) => a.description).map((a) => a.id);
|
|
57
|
+
console.log(`June app: ${config.site?.name ?? "(unnamed)"}`);
|
|
58
|
+
console.log(`\nRoutes (${routes.length}):`);
|
|
59
|
+
for (const r of routes) console.log(` ${r}`);
|
|
60
|
+
if (agent.enabled) {
|
|
61
|
+
console.log(`\nAgent surface:`);
|
|
62
|
+
if (agent.discovery) console.log(` discovery /llms.txt · /sitemap.xml · /.well-known/api-catalog`);
|
|
63
|
+
if (agent.mcp) console.log(` mcp /mcp (tools: ${tools.length ? tools.join(", ") : "none"})`);
|
|
64
|
+
} else console.log(`\nAgent surface: disabled`);
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
async function run(argv) {
|
|
68
|
+
const { verb, positional, flags } = parse(argv);
|
|
69
|
+
const root = appRoot(positional);
|
|
70
|
+
switch (verb) {
|
|
71
|
+
case "dev": {
|
|
72
|
+
if (!existsSync(join(root, "app"))) {
|
|
73
|
+
console.error(`june dev: ${root} doesn't look like a June app (no app/ directory).`);
|
|
74
|
+
if (/^\d+$/.test(positional[0] ?? "")) console.error(` to choose a port, use --port: june dev --port ${positional[0]} · npm run dev -- --port ${positional[0]}`);
|
|
75
|
+
return 1;
|
|
76
|
+
}
|
|
77
|
+
if (!process.env.JUNE_DEV_CHILD && !flags["no-watch"]) {
|
|
78
|
+
const { superviseDev } = await import("./watch-CeyzNVKv.js");
|
|
79
|
+
return superviseDev(root);
|
|
80
|
+
}
|
|
81
|
+
const { startDevServer } = await import("@junejs/server");
|
|
82
|
+
await startDevServer({
|
|
83
|
+
appDir: join(root, "app"),
|
|
84
|
+
port: flags.port ? Number(flags.port) : 3e3
|
|
85
|
+
});
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
case "build": {
|
|
89
|
+
const { juneBuild } = await import("@junejs/server");
|
|
90
|
+
const r = await juneBuild(root, typeof flags.out === "string" ? { outDir: resolve(flags.out) } : {});
|
|
91
|
+
console.log(`built ${r.outFile} (${r.routes.length} routes, ${r.dynamicRoutes.length} dynamic, ${r.prerendered.length} prerendered)`);
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
case "deploy": {
|
|
95
|
+
const { juneDeploy } = await import("@junejs/server");
|
|
96
|
+
const r = await juneDeploy(root, {
|
|
97
|
+
dryRun: !!flags["dry-run"],
|
|
98
|
+
skipMigrate: !!flags["skip-migrate"],
|
|
99
|
+
allowDestructive: !!flags["allow-destructive"],
|
|
100
|
+
prod: !!flags["prod"]
|
|
101
|
+
});
|
|
102
|
+
console.log(r.url ? `deployed → ${r.url}` : r.dryRun ? "dry-run ok" : "deployed");
|
|
103
|
+
return 0;
|
|
104
|
+
}
|
|
105
|
+
case "gen": {
|
|
106
|
+
const { generateContent } = await import("@junejs/server");
|
|
107
|
+
const cols = await generateContent(root);
|
|
108
|
+
console.log(cols.length ? `generated content: ${cols.join(", ")}` : "no content/ collections");
|
|
109
|
+
const { generateMessages } = await import("./messages-DfAlYpfN.js");
|
|
110
|
+
const locales = await generateMessages(root);
|
|
111
|
+
if (locales) console.log(`generated messages: ${locales.join(", ")}`);
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
case "db": {
|
|
115
|
+
const sub = positional[0];
|
|
116
|
+
const dbRoot = positional[1] ? resolve(positional[1]) : process.cwd();
|
|
117
|
+
if (sub === "migrate") {
|
|
118
|
+
const { loadJuneConfig, migrateApp, blockedMessage } = await import("@junejs/server");
|
|
119
|
+
const config = await loadJuneConfig(dbRoot);
|
|
120
|
+
if (!config.resources?.db) {
|
|
121
|
+
console.error("june db migrate: no `db` resource declared in june.config.ts.");
|
|
122
|
+
return 1;
|
|
123
|
+
}
|
|
124
|
+
const r = await migrateApp(dbRoot, config, { allowDestructive: !!flags["allow-destructive"] });
|
|
125
|
+
if (r?.applied.length) console.log(`migrated: ${r.applied.join(", ")}`);
|
|
126
|
+
else if (r && !r.blocked) console.log("migrations up to date");
|
|
127
|
+
if (r?.blocked) {
|
|
128
|
+
console.error(blockedMessage(r.blocked));
|
|
129
|
+
return 1;
|
|
130
|
+
}
|
|
131
|
+
return 0;
|
|
132
|
+
}
|
|
133
|
+
if (sub === "types") {
|
|
134
|
+
const { loadJuneConfig, typesApp } = await import("@junejs/server");
|
|
135
|
+
const config = await loadJuneConfig(dbRoot);
|
|
136
|
+
if (!config.resources?.db) {
|
|
137
|
+
console.error("june db types: no `db` resource declared in june.config.ts.");
|
|
138
|
+
return 1;
|
|
139
|
+
}
|
|
140
|
+
if (!config.dataLayer?.emitTypes) {
|
|
141
|
+
console.error("june db types: the configured dataLayer doesn't generate types (declare `dataLayer: junoDataLayer()` in june.config.ts).");
|
|
142
|
+
return 1;
|
|
143
|
+
}
|
|
144
|
+
const text = await typesApp(dbRoot, config);
|
|
145
|
+
if (text == null) {
|
|
146
|
+
console.log("nothing to generate");
|
|
147
|
+
return 0;
|
|
148
|
+
}
|
|
149
|
+
await writeFile(join(dbRoot, "db", "schema.d.ts"), text);
|
|
150
|
+
console.log("wrote db/schema.d.ts");
|
|
151
|
+
return 0;
|
|
152
|
+
}
|
|
153
|
+
console.error(`june db: unknown subcommand "${sub ?? ""}" (try: june db migrate | june db types)`);
|
|
154
|
+
return 1;
|
|
155
|
+
}
|
|
156
|
+
case "info": return info(root);
|
|
157
|
+
case "":
|
|
158
|
+
case "help":
|
|
159
|
+
case "--help":
|
|
160
|
+
case "-h":
|
|
161
|
+
console.log(HELP);
|
|
162
|
+
return 0;
|
|
163
|
+
default:
|
|
164
|
+
console.error(`june: unknown command "${verb}"\n`);
|
|
165
|
+
console.log(HELP);
|
|
166
|
+
return 1;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
//#endregion
|
|
170
|
+
export { parse, run };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
//#region src/messages.ts
|
|
7
|
+
/** Generate `app/_messages.ts` from `messages/*.json`. Returns the locales, or
|
|
8
|
+
* null when there's no `messages/` dir or @junejs/i18n isn't installed. */
|
|
9
|
+
async function generateMessages(root) {
|
|
10
|
+
const messagesDir = join(root, "messages");
|
|
11
|
+
if (!existsSync(messagesDir)) return null;
|
|
12
|
+
let codegen;
|
|
13
|
+
try {
|
|
14
|
+
codegen = await import(pathToFileURL(createRequire(resolve(root, "package.json")).resolve("@junejs/i18n/codegen")).href);
|
|
15
|
+
} catch {
|
|
16
|
+
console.warn("[june] messages/ found but @junejs/i18n is not installed — skipping (run: bun add @junejs/i18n)");
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const { loadJuneConfig } = await import("@junejs/server");
|
|
20
|
+
const defaultLocale = (await loadJuneConfig(root)).i18n?.defaultLocale ?? "en";
|
|
21
|
+
const { code, locales } = codegen.generateMessagesModule(messagesDir, { defaultLocale });
|
|
22
|
+
await writeFile(join(root, "app", "_messages.ts"), code);
|
|
23
|
+
return locales;
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { generateMessages };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { existsSync, watch } from "node:fs";
|
|
2
|
+
import { join, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
//#region src/watch.ts
|
|
5
|
+
const IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
6
|
+
"node_modules",
|
|
7
|
+
".git",
|
|
8
|
+
"dist",
|
|
9
|
+
".june"
|
|
10
|
+
]);
|
|
11
|
+
function ignoredPath(file) {
|
|
12
|
+
const parts = file.split(sep);
|
|
13
|
+
if (parts.some((p) => IGNORED_DIRS.has(p))) return true;
|
|
14
|
+
if (file.endsWith(".css")) return true;
|
|
15
|
+
return parts.length === 2 && parts[0] === "app" && (/^_content\.[^/]+$/.test(parts[1]) || parts[1].endsWith(".gen.ts"));
|
|
16
|
+
}
|
|
17
|
+
function superviseDev(root) {
|
|
18
|
+
let child;
|
|
19
|
+
let restarting = false;
|
|
20
|
+
const start = () => {
|
|
21
|
+
child = spawn(process.execPath, process.argv.slice(1), {
|
|
22
|
+
stdio: "inherit",
|
|
23
|
+
env: {
|
|
24
|
+
...process.env,
|
|
25
|
+
JUNE_DEV_CHILD: "1"
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
child.on("exit", (code) => {
|
|
29
|
+
if (restarting) return;
|
|
30
|
+
console.error(`[june] dev server exited (${code ?? "?"}) — waiting for changes`);
|
|
31
|
+
child = void 0;
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
const restart = async (file, contentChanged) => {
|
|
35
|
+
if (restarting) return;
|
|
36
|
+
restarting = true;
|
|
37
|
+
if (contentChanged) try {
|
|
38
|
+
const { generateContent } = await import("@junejs/server");
|
|
39
|
+
await generateContent(root);
|
|
40
|
+
} catch (err) {
|
|
41
|
+
console.error("[june] content regen failed:", err);
|
|
42
|
+
}
|
|
43
|
+
if (file.startsWith(`messages${sep}`)) try {
|
|
44
|
+
const { generateMessages } = await import("./messages-DfAlYpfN.js");
|
|
45
|
+
await generateMessages(root);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.error("[june] messages regen failed:", err);
|
|
48
|
+
}
|
|
49
|
+
console.log(`[june] ${file} changed — restarting`);
|
|
50
|
+
const c = child;
|
|
51
|
+
if (!c || c.exitCode !== null || c.signalCode !== null) {
|
|
52
|
+
restarting = false;
|
|
53
|
+
start();
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
c.once("exit", () => {
|
|
57
|
+
restarting = false;
|
|
58
|
+
start();
|
|
59
|
+
});
|
|
60
|
+
c.kill();
|
|
61
|
+
};
|
|
62
|
+
let pending;
|
|
63
|
+
let timer;
|
|
64
|
+
const schedule = (file, content) => {
|
|
65
|
+
pending = {
|
|
66
|
+
file,
|
|
67
|
+
content: content || (pending?.content ?? false)
|
|
68
|
+
};
|
|
69
|
+
clearTimeout(timer);
|
|
70
|
+
timer = setTimeout(() => void restart(pending.file, pending.content), 80);
|
|
71
|
+
};
|
|
72
|
+
watch(root, { recursive: true }, (_event, file) => {
|
|
73
|
+
if (!file || ignoredPath(file)) return;
|
|
74
|
+
schedule(file, file.startsWith(`content${sep}`));
|
|
75
|
+
});
|
|
76
|
+
(async () => {
|
|
77
|
+
try {
|
|
78
|
+
const { loadJuneConfig } = await import("@junejs/server");
|
|
79
|
+
const config = await loadJuneConfig(root);
|
|
80
|
+
for (const s of config.content?.sources ?? []) {
|
|
81
|
+
const dir = resolve(root, s.dir);
|
|
82
|
+
if (!existsSync(dir)) continue;
|
|
83
|
+
watch(dir, { recursive: true }, (_event, file) => {
|
|
84
|
+
if (!file || ignoredPath(file)) return;
|
|
85
|
+
schedule(relative(root, join(dir, file)), true);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
} catch {}
|
|
89
|
+
})();
|
|
90
|
+
for (const sig of ["SIGINT", "SIGTERM"]) process.on(sig, () => {
|
|
91
|
+
child?.kill(sig);
|
|
92
|
+
process.exit(0);
|
|
93
|
+
});
|
|
94
|
+
start();
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
97
|
+
export { superviseDev };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junejs/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.51-dev.1",
|
|
4
4
|
"description": "The `june` CLI — dev / build / deploy / gen / info. A thin bin over @junejs/server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -9,19 +9,26 @@
|
|
|
9
9
|
"june": "./bin.mjs"
|
|
10
10
|
},
|
|
11
11
|
"exports": {
|
|
12
|
-
".":
|
|
12
|
+
".": {
|
|
13
|
+
"source": "./src/cli.ts",
|
|
14
|
+
"bun": "./src/cli.ts",
|
|
15
|
+
"types": "./dist/cli.d.ts",
|
|
16
|
+
"default": "./dist/cli.js"
|
|
17
|
+
}
|
|
13
18
|
},
|
|
14
19
|
"files": [
|
|
15
20
|
"src",
|
|
16
|
-
"bin.mjs"
|
|
21
|
+
"bin.mjs",
|
|
22
|
+
"dist"
|
|
17
23
|
],
|
|
18
24
|
"scripts": {
|
|
19
25
|
"test": "bun test",
|
|
20
|
-
"typecheck": "tsc --noEmit"
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"build": "tsdown"
|
|
21
28
|
},
|
|
22
29
|
"dependencies": {
|
|
23
|
-
"@junejs/core": ">=0.0.
|
|
24
|
-
"@junejs/server": ">=0.0.
|
|
30
|
+
"@junejs/core": ">=0.1.0-dev.4",
|
|
31
|
+
"@junejs/server": ">=0.1.0-dev.5"
|
|
25
32
|
},
|
|
26
33
|
"repository": {
|
|
27
34
|
"type": "git",
|