@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.
@@ -1,8 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import process from "node:process";
2
+ import { runCliEntrypoint } from "../src/shared/cliEntrypoint.js";
3
3
  import { runCli } from "../src/shared/index.js";
4
4
 
5
- const exitCode = await runCli(process.argv.slice(2));
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.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 };
@@ -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 createServer() {
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, onMounted, ref } from "vue";
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: [vue()],
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
  },
@@ -0,0 +1,9 @@
1
+ function toPositiveInt(value, fallback) {
2
+ const parsed = Number.parseInt(String(value || "").trim(), 10);
3
+ if (Number.isInteger(parsed) && parsed > 0) {
4
+ return parsed;
5
+ }
6
+ return fallback;
7
+ }
8
+
9
+ export { toPositiveInt };