@jskit-ai/create-app 0.1.1 → 0.1.2
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/bin/jskit-create-app.js +2 -5
- package/package.json +2 -1
- package/src/shared/cliEntrypoint.js +25 -0
- package/src/shared/index.js +1 -11
- package/templates/base-shell/package.json +2 -0
- package/templates/base-shell/package.json.ACTUAL_CORRECT +38 -0
- package/templates/base-shell/server.js +76 -4
- package/templates/base-shell/src/App.vue +24 -0
- package/templates/base-shell/src/main.js +2 -28
- package/templates/base-shell/tests/server/minimalShell.contract.test.js +4 -1
- package/templates/base-shell/tests/server/smoke.test.js +1 -1
- package/templates/base-shell/vite.config.mjs +23 -9
- package/templates/base-shell/vite.shared.mjs +9 -0
package/bin/jskit-create-app.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
2
|
+
import { runCliEntrypoint } from "../src/shared/cliEntrypoint.js";
|
|
3
3
|
import { runCli } from "../src/shared/index.js";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
if (exitCode !== 0) {
|
|
7
|
-
process.exit(exitCode);
|
|
8
|
-
}
|
|
5
|
+
await runCliEntrypoint(runCli);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/create-app",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Scaffold minimal JSKIT app shells.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"exports": {
|
|
19
19
|
".": "./src/shared/index.js"
|
|
20
20
|
},
|
|
21
|
+
"dependencies": {},
|
|
21
22
|
"engines": {
|
|
22
23
|
"node": "20.x"
|
|
23
24
|
},
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
|
|
3
|
+
function shellQuote(value) {
|
|
4
|
+
const raw = String(value ?? "");
|
|
5
|
+
if (!raw) {
|
|
6
|
+
return "''";
|
|
7
|
+
}
|
|
8
|
+
if (/^[A-Za-z0-9_./:=+,-]+$/.test(raw)) {
|
|
9
|
+
return raw;
|
|
10
|
+
}
|
|
11
|
+
return `'${raw.replace(/'/g, "'\\''")}'`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function runCliEntrypoint(runCli, argv = process.argv.slice(2)) {
|
|
15
|
+
if (typeof runCli !== "function") {
|
|
16
|
+
throw new TypeError("runCliEntrypoint requires a runCli function");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const exitCode = await runCli(argv);
|
|
20
|
+
if (exitCode !== 0) {
|
|
21
|
+
process.exit(exitCode);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export { shellQuote, runCliEntrypoint };
|
package/src/shared/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import { createInterface } from "node:readline/promises";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { shellQuote } from "./cliEntrypoint.js";
|
|
6
7
|
|
|
7
8
|
const DEFAULT_TEMPLATE = "base-shell";
|
|
8
9
|
const DEFAULT_INITIAL_BUNDLES = "none";
|
|
@@ -20,17 +21,6 @@ function createCliError(message, { showUsage = false, exitCode = 1 } = {}) {
|
|
|
20
21
|
return error;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
function shellQuote(value) {
|
|
24
|
-
const raw = String(value ?? "");
|
|
25
|
-
if (!raw) {
|
|
26
|
-
return "''";
|
|
27
|
-
}
|
|
28
|
-
if (/^[A-Za-z0-9_./:=+,-]+$/.test(raw)) {
|
|
29
|
-
return raw;
|
|
30
|
-
}
|
|
31
|
-
return `'${raw.replace(/'/g, "'\\''")}'`;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
24
|
function toAppTitle(appName) {
|
|
35
25
|
const words = String(appName)
|
|
36
26
|
.trim()
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@jskit-ai/app-scripts": "0.1.0",
|
|
26
|
+
"@jskit-ai/server-runtime-core": "file:node_modules/@jskit-ai/jskit/packages/runtime/server-runtime-core",
|
|
27
|
+
"@jskit-ai/surface-routing": "file:node_modules/@jskit-ai/jskit/packages/surface-routing",
|
|
26
28
|
"fastify": "^5.7.4",
|
|
27
29
|
"vue": "^3.5.13"
|
|
28
30
|
},
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__APP_NAME__",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Minimal JSKIT base app (Fastify + Vue)",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": "20.x"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"jskit": "node_modules/@jskit-ai/jskit/packages/tooling/jskit/bin/jskit.js"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"server": "jskit-app-scripts server",
|
|
15
|
+
"start": "jskit-app-scripts start",
|
|
16
|
+
"dev": "jskit-app-scripts dev",
|
|
17
|
+
"build": "jskit-app-scripts build",
|
|
18
|
+
"preview": "jskit-app-scripts preview",
|
|
19
|
+
"lint": "jskit-app-scripts lint",
|
|
20
|
+
"lint:process-env": "jskit-app-scripts lint:process-env",
|
|
21
|
+
"test": "jskit-app-scripts test",
|
|
22
|
+
"test:client": "jskit-app-scripts test:client"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@jskit-ai/app-scripts": "0.1.0",
|
|
26
|
+
"@jskit-ai/server-runtime-core": "file:node_modules/@jskit-ai/jskit/packages/runtime/server-runtime-core",
|
|
27
|
+
"fastify": "^5.7.4",
|
|
28
|
+
"vue": "^3.5.13"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@jskit-ai/config-eslint": "0.1.0",
|
|
32
|
+
"@jskit-ai/jskit": "github:mobily-enterprises/jskit-ai",
|
|
33
|
+
"@vitejs/plugin-vue": "^5.2.1",
|
|
34
|
+
"eslint": "^9.39.1",
|
|
35
|
+
"vite": "^6.1.0",
|
|
36
|
+
"vitest": "^4.0.18"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -1,15 +1,87 @@
|
|
|
1
1
|
import Fastify from "fastify";
|
|
2
2
|
import { resolveRuntimeEnv } from "./server/lib/runtimeEnv.js";
|
|
3
|
+
import { registerApiRouteDefinitions } from "@jskit-ai/server-runtime-core/apiRouteRegistration";
|
|
4
|
+
import { createServerRuntimeFromApp, applyContributedRuntimeLifecycle } from "@jskit-ai/server-runtime-core/serverContributions";
|
|
5
|
+
import path from "node:path";
|
|
3
6
|
|
|
4
|
-
function
|
|
5
|
-
const app = Fastify({ logger: true });
|
|
6
|
-
|
|
7
|
+
function registerFallbackHealthRoute(app) {
|
|
7
8
|
app.get("/api/v1/health", async () => {
|
|
8
9
|
return {
|
|
9
10
|
ok: true,
|
|
10
11
|
app: "__APP_NAME__"
|
|
11
12
|
};
|
|
12
13
|
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function registerContributedRuntime(app, { appRoot, runtimeEnv }) {
|
|
17
|
+
try {
|
|
18
|
+
const composed = await createServerRuntimeFromApp({
|
|
19
|
+
appRoot,
|
|
20
|
+
strict: false,
|
|
21
|
+
dependencies: {
|
|
22
|
+
env: runtimeEnv,
|
|
23
|
+
logger: app.log
|
|
24
|
+
},
|
|
25
|
+
routeConfig: {}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const routeCount = Array.isArray(composed.routes) ? composed.routes.length : 0;
|
|
29
|
+
if (routeCount > 0) {
|
|
30
|
+
registerApiRouteDefinitions(app, {
|
|
31
|
+
routes: composed.routes
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const lifecycleResult = await applyContributedRuntimeLifecycle({
|
|
36
|
+
app,
|
|
37
|
+
runtimeResult: composed,
|
|
38
|
+
dependencies: {
|
|
39
|
+
env: runtimeEnv,
|
|
40
|
+
logger: app.log
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
app.log.info(
|
|
45
|
+
{
|
|
46
|
+
routeCount,
|
|
47
|
+
pluginCount: lifecycleResult.pluginCount,
|
|
48
|
+
workerCount: lifecycleResult.workerCount,
|
|
49
|
+
onBootCount: lifecycleResult.onBootCount,
|
|
50
|
+
packageOrder: composed.packageOrder
|
|
51
|
+
},
|
|
52
|
+
"Registered JSKIT contributed server runtime."
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
enabled: true,
|
|
57
|
+
routeCount,
|
|
58
|
+
pluginCount: lifecycleResult.pluginCount,
|
|
59
|
+
workerCount: lifecycleResult.workerCount,
|
|
60
|
+
onBootCount: lifecycleResult.onBootCount
|
|
61
|
+
};
|
|
62
|
+
} catch (error) {
|
|
63
|
+
const message = String(error?.message || "");
|
|
64
|
+
if (message.includes("Lock file not found:")) {
|
|
65
|
+
return {
|
|
66
|
+
enabled: false,
|
|
67
|
+
routeCount: 0
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function createServer() {
|
|
75
|
+
const app = Fastify({ logger: true });
|
|
76
|
+
const runtimeEnv = resolveRuntimeEnv();
|
|
77
|
+
const appRoot = path.resolve(process.cwd());
|
|
78
|
+
const contributed = await registerContributedRuntime(app, {
|
|
79
|
+
appRoot,
|
|
80
|
+
runtimeEnv
|
|
81
|
+
});
|
|
82
|
+
if (!contributed.enabled || contributed.routeCount < 1) {
|
|
83
|
+
registerFallbackHealthRoute(app);
|
|
84
|
+
}
|
|
13
85
|
|
|
14
86
|
return app;
|
|
15
87
|
}
|
|
@@ -18,7 +90,7 @@ async function startServer(options = {}) {
|
|
|
18
90
|
const runtimeEnv = resolveRuntimeEnv();
|
|
19
91
|
const port = Number(options?.port) || runtimeEnv.PORT;
|
|
20
92
|
const host = String(options?.host || "").trim() || runtimeEnv.HOST;
|
|
21
|
-
const app = createServer();
|
|
93
|
+
const app = await createServer();
|
|
22
94
|
await app.listen({ port, host });
|
|
23
95
|
return app;
|
|
24
96
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { onMounted, ref } from "vue";
|
|
3
|
+
|
|
4
|
+
const appTitle = "__APP_TITLE__";
|
|
5
|
+
const health = ref("loading...");
|
|
6
|
+
|
|
7
|
+
onMounted(async () => {
|
|
8
|
+
try {
|
|
9
|
+
const response = await fetch("/api/v1/health");
|
|
10
|
+
const payload = await response.json();
|
|
11
|
+
health.value = payload?.ok ? "ok" : "unhealthy";
|
|
12
|
+
} catch {
|
|
13
|
+
health.value = "unreachable";
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<template>
|
|
19
|
+
<main style="font-family: sans-serif; max-width: 48rem; margin: 3rem auto; padding: 0 1rem;">
|
|
20
|
+
<h1>{{ appTitle }}</h1>
|
|
21
|
+
<p>Minimal starter shell is running.</p>
|
|
22
|
+
<p><strong>Health:</strong> {{ health }}</p>
|
|
23
|
+
</main>
|
|
24
|
+
</template>
|
|
@@ -1,30 +1,4 @@
|
|
|
1
|
-
import { createApp
|
|
2
|
-
|
|
3
|
-
const App = {
|
|
4
|
-
setup() {
|
|
5
|
-
const health = ref("loading...");
|
|
6
|
-
|
|
7
|
-
onMounted(async () => {
|
|
8
|
-
try {
|
|
9
|
-
const response = await fetch("/api/v1/health");
|
|
10
|
-
const payload = await response.json();
|
|
11
|
-
health.value = payload?.ok ? "ok" : "unhealthy";
|
|
12
|
-
} catch {
|
|
13
|
-
health.value = "unreachable";
|
|
14
|
-
}
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
return {
|
|
18
|
-
health
|
|
19
|
-
};
|
|
20
|
-
},
|
|
21
|
-
template: `
|
|
22
|
-
<main style="font-family: sans-serif; max-width: 48rem; margin: 3rem auto; padding: 0 1rem;">
|
|
23
|
-
<h1>__APP_TITLE__</h1>
|
|
24
|
-
<p>Minimal starter shell is running.</p>
|
|
25
|
-
<p><strong>Health:</strong> {{ health }}</p>
|
|
26
|
-
</main>
|
|
27
|
-
`
|
|
28
|
-
};
|
|
1
|
+
import { createApp } from "vue";
|
|
2
|
+
import App from "./App.vue";
|
|
29
3
|
|
|
30
4
|
createApp(App).mount("#app");
|
|
@@ -10,6 +10,7 @@ const APP_ROOT = path.resolve(__dirname, "../..");
|
|
|
10
10
|
|
|
11
11
|
const EXPECTED_RUNTIME_DEPENDENCIES = Object.freeze([
|
|
12
12
|
"@jskit-ai/app-scripts",
|
|
13
|
+
"@jskit-ai/server-runtime-core",
|
|
13
14
|
"fastify",
|
|
14
15
|
"vue"
|
|
15
16
|
]);
|
|
@@ -33,11 +34,13 @@ const EXPECTED_TOP_LEVEL_ENTRIES = Object.freeze([
|
|
|
33
34
|
"gitignore",
|
|
34
35
|
"index.html",
|
|
35
36
|
"package.json",
|
|
37
|
+
"package.json.ACTUAL_CORRECT",
|
|
36
38
|
"server.js",
|
|
37
39
|
"server",
|
|
38
40
|
"src",
|
|
39
41
|
"tests",
|
|
40
|
-
"vite.config.mjs"
|
|
42
|
+
"vite.config.mjs",
|
|
43
|
+
"vite.shared.mjs"
|
|
41
44
|
]);
|
|
42
45
|
|
|
43
46
|
async function readPackageJson() {
|
|
@@ -3,7 +3,7 @@ import test from "node:test";
|
|
|
3
3
|
import { createServer } from "../../server.js";
|
|
4
4
|
|
|
5
5
|
test("GET /api/v1/health returns ok payload", async () => {
|
|
6
|
-
const app = createServer();
|
|
6
|
+
const app = await createServer();
|
|
7
7
|
const response = await app.inject({
|
|
8
8
|
method: "GET",
|
|
9
9
|
url: "/api/v1/health"
|
|
@@ -1,19 +1,33 @@
|
|
|
1
1
|
import { defineConfig } from "vite";
|
|
2
2
|
import vue from "@vitejs/plugin-vue";
|
|
3
|
-
|
|
4
|
-
function toPositiveInt(value, fallback) {
|
|
5
|
-
const parsed = Number.parseInt(String(value || "").trim(), 10);
|
|
6
|
-
if (Number.isInteger(parsed) && parsed > 0) {
|
|
7
|
-
return parsed;
|
|
8
|
-
}
|
|
9
|
-
return fallback;
|
|
10
|
-
}
|
|
3
|
+
import { toPositiveInt } from "./vite.shared.mjs";
|
|
11
4
|
|
|
12
5
|
const devPort = toPositiveInt(process.env.VITE_DEV_PORT, 5173);
|
|
13
6
|
const apiProxyTarget = String(process.env.VITE_API_PROXY_TARGET || "").trim() || "http://localhost:3000";
|
|
7
|
+
const clientEntry = (() => {
|
|
8
|
+
const normalized = String(process.env.VITE_CLIENT_ENTRY || "").trim();
|
|
9
|
+
if (!normalized) {
|
|
10
|
+
return "/src/main.js";
|
|
11
|
+
}
|
|
12
|
+
if (normalized.startsWith("/")) {
|
|
13
|
+
return normalized;
|
|
14
|
+
}
|
|
15
|
+
if (normalized.startsWith("src/")) {
|
|
16
|
+
return `/${normalized}`;
|
|
17
|
+
}
|
|
18
|
+
return `/src/${normalized}`;
|
|
19
|
+
})();
|
|
14
20
|
|
|
15
21
|
export default defineConfig({
|
|
16
|
-
plugins: [
|
|
22
|
+
plugins: [
|
|
23
|
+
vue(),
|
|
24
|
+
{
|
|
25
|
+
name: "jskit-client-entry",
|
|
26
|
+
transformIndexHtml(source) {
|
|
27
|
+
return String(source || "").replace(/\/src\/main\.js/g, clientEntry);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
],
|
|
17
31
|
test: {
|
|
18
32
|
include: ["tests/client/**/*.vitest.js"]
|
|
19
33
|
},
|