@jskit-ai/create-app 0.1.26 → 0.1.28
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/package.json +1 -1
- package/src/server/index.js +2 -2
- package/templates/base-shell/README.md +7 -1
- package/templates/base-shell/package.json +1 -0
- package/templates/base-shell/packages/main/src/server/support/loadAppConfig.js +3 -49
- package/templates/base-shell/scripts/link-local-jskit-packages.sh +64 -2
- package/templates/base-shell/scripts/release.sh +95 -0
- package/templates/base-shell/scripts/update-jskit-packages.sh +1 -1
- package/templates/base-shell/server.js +7 -9
package/package.json
CHANGED
package/src/server/index.js
CHANGED
|
@@ -53,7 +53,7 @@ function buildInitialBundleCommands(initialBundles) {
|
|
|
53
53
|
|
|
54
54
|
const commands = [];
|
|
55
55
|
if (normalizedPreset === "auth") {
|
|
56
|
-
commands.push("npx jskit add auth-base
|
|
56
|
+
commands.push("npx jskit add auth-base");
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
return commands;
|
|
@@ -634,7 +634,7 @@ export async function runCli(
|
|
|
634
634
|
} else {
|
|
635
635
|
stdout.write("First of all run npm install.:\n");
|
|
636
636
|
stdout.write("Then add framework capabilities:\n");
|
|
637
|
-
stdout.write("- npx jskit add auth-base
|
|
637
|
+
stdout.write("- npx jskit add auth-base\n");
|
|
638
638
|
stdout.write("- npx jskit list\n");
|
|
639
639
|
stdout.write("Run server and client to see it in action:\n");
|
|
640
640
|
stdout.write("- npm run dev\n");
|
|
@@ -15,6 +15,12 @@ Refresh JSKIT dependencies to the latest published versions:
|
|
|
15
15
|
npm run jskit:update
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
+
Automate update + PR + merge release flow:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm run release
|
|
22
|
+
```
|
|
23
|
+
|
|
18
24
|
## Server
|
|
19
25
|
|
|
20
26
|
```bash
|
|
@@ -29,5 +35,5 @@ App configuration files:
|
|
|
29
35
|
## Add Capabilities
|
|
30
36
|
|
|
31
37
|
```bash
|
|
32
|
-
npx jskit add auth-base
|
|
38
|
+
npx jskit add auth-base
|
|
33
39
|
```
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"test": "node --test",
|
|
28
28
|
"test:client": "vitest run tests/client",
|
|
29
29
|
"verify": "npm run lint && npm run test && npm run test:client && npm run build && npx jskit doctor",
|
|
30
|
+
"release": "bash ./scripts/release.sh",
|
|
30
31
|
"jskit:update": "bash ./scripts/update-jskit-packages.sh"
|
|
31
32
|
},
|
|
32
33
|
"dependencies": {
|
|
@@ -1,54 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
|
-
|
|
5
|
-
async function fileExists(absolutePath) {
|
|
6
|
-
try {
|
|
7
|
-
await access(absolutePath, fsConstants.F_OK);
|
|
8
|
-
return true;
|
|
9
|
-
} catch {
|
|
10
|
-
return false;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async function resolveAppRootFrom(moduleDirectory) {
|
|
15
|
-
let currentDirectory = path.resolve(moduleDirectory);
|
|
16
|
-
|
|
17
|
-
while (true) {
|
|
18
|
-
const candidateConfigPath = path.join(currentDirectory, "config", "public.js");
|
|
19
|
-
if (await fileExists(candidateConfigPath)) {
|
|
20
|
-
return currentDirectory;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const parentDirectory = path.dirname(currentDirectory);
|
|
24
|
-
if (parentDirectory === currentDirectory) {
|
|
25
|
-
throw new Error("Unable to locate app root (missing config/public.js).");
|
|
26
|
-
}
|
|
27
|
-
currentDirectory = parentDirectory;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async function loadConfigModule(absolutePath) {
|
|
32
|
-
if (!(await fileExists(absolutePath))) {
|
|
33
|
-
return {};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const loadedModule = await import(pathToFileURL(absolutePath).href);
|
|
37
|
-
const loadedConfig = loadedModule?.config;
|
|
38
|
-
return loadedConfig && typeof loadedConfig === "object" && !Array.isArray(loadedConfig) ? loadedConfig : {};
|
|
39
|
-
}
|
|
1
|
+
import { loadAppConfigFromModuleUrl } from "@jskit-ai/kernel/server/support";
|
|
40
2
|
|
|
41
3
|
async function loadAppConfig({ moduleUrl = import.meta.url } = {}) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const [publicConfig, serverConfig] = await Promise.all([
|
|
45
|
-
loadConfigModule(path.join(appRoot, "config", "public.js")),
|
|
46
|
-
loadConfigModule(path.join(appRoot, "config", "server.js"))
|
|
47
|
-
]);
|
|
48
|
-
|
|
49
|
-
return Object.freeze({
|
|
50
|
-
...publicConfig,
|
|
51
|
-
...serverConfig
|
|
4
|
+
return loadAppConfigFromModuleUrl({
|
|
5
|
+
moduleUrl
|
|
52
6
|
});
|
|
53
7
|
}
|
|
54
8
|
|
|
@@ -87,8 +87,7 @@ for (const parentDirectory of parentDirectories) {
|
|
|
87
87
|
|
|
88
88
|
const packageRoot = path.join(parentDirectory, entry.name);
|
|
89
89
|
const packageJsonPath = path.join(packageRoot, "package.json");
|
|
90
|
-
|
|
91
|
-
if (!fs.existsSync(packageJsonPath) || !fs.existsSync(descriptorPath)) {
|
|
90
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
92
91
|
continue;
|
|
93
92
|
}
|
|
94
93
|
|
|
@@ -120,6 +119,68 @@ for (const [packageDirName, packageRoot] of packageMap.entries()) {
|
|
|
120
119
|
NODE
|
|
121
120
|
}
|
|
122
121
|
|
|
122
|
+
link_package_bin_entries() {
|
|
123
|
+
local package_dir_name="$1"
|
|
124
|
+
local source_dir="$2"
|
|
125
|
+
|
|
126
|
+
node - "$APP_ROOT" "$package_dir_name" "$source_dir" <<'NODE'
|
|
127
|
+
const fs = require("node:fs");
|
|
128
|
+
const path = require("node:path");
|
|
129
|
+
|
|
130
|
+
const appRoot = process.argv[2];
|
|
131
|
+
const packageDirName = process.argv[3];
|
|
132
|
+
const sourceDir = process.argv[4];
|
|
133
|
+
|
|
134
|
+
const packageJsonPath = path.join(sourceDir, "package.json");
|
|
135
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
136
|
+
process.exit(0);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let packageJson = {};
|
|
140
|
+
try {
|
|
141
|
+
packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
142
|
+
} catch {
|
|
143
|
+
process.exit(0);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const rawBin = packageJson?.bin;
|
|
147
|
+
let binEntries = [];
|
|
148
|
+
if (typeof rawBin === "string") {
|
|
149
|
+
binEntries = [[packageDirName, rawBin]];
|
|
150
|
+
} else if (rawBin && typeof rawBin === "object" && !Array.isArray(rawBin)) {
|
|
151
|
+
binEntries = Object.entries(rawBin);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (binEntries.length < 1) {
|
|
155
|
+
process.exit(0);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const binDir = path.join(appRoot, "node_modules", ".bin");
|
|
159
|
+
const packageRoot = path.join(appRoot, "node_modules", "@jskit-ai", packageDirName);
|
|
160
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
161
|
+
|
|
162
|
+
for (const [rawBinName, rawBinTarget] of binEntries) {
|
|
163
|
+
const binName = String(rawBinName || "").trim();
|
|
164
|
+
const binTarget = String(rawBinTarget || "").trim();
|
|
165
|
+
if (!binName || !binTarget) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const absoluteTarget = path.join(packageRoot, binTarget);
|
|
170
|
+
if (!fs.existsSync(absoluteTarget)) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const binPath = path.join(binDir, binName);
|
|
175
|
+
fs.rmSync(binPath, { force: true, recursive: true });
|
|
176
|
+
|
|
177
|
+
const relativeTarget = path.relative(binDir, absoluteTarget) || absoluteTarget;
|
|
178
|
+
fs.symlinkSync(relativeTarget, binPath);
|
|
179
|
+
process.stdout.write(`[link-local] linked bin ${binName} -> ${relativeTarget}\n`);
|
|
180
|
+
}
|
|
181
|
+
NODE
|
|
182
|
+
}
|
|
183
|
+
|
|
123
184
|
linked_count=0
|
|
124
185
|
|
|
125
186
|
mkdir -p "$SCOPE_DIR"
|
|
@@ -132,6 +193,7 @@ while IFS=$'\t' read -r package_dir_name source_dir; do
|
|
|
132
193
|
rm -rf "$target_path"
|
|
133
194
|
ln -s "$source_dir" "$target_path"
|
|
134
195
|
echo "[link-local] linked @jskit-ai/$package_dir_name -> $source_dir"
|
|
196
|
+
link_package_bin_entries "$package_dir_name" "$source_dir"
|
|
135
197
|
linked_count=$((linked_count + 1))
|
|
136
198
|
done < <(discover_local_package_map)
|
|
137
199
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
APP_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
|
|
6
|
+
require_command() {
|
|
7
|
+
local name="$1"
|
|
8
|
+
if ! command -v "$name" >/dev/null 2>&1; then
|
|
9
|
+
echo "[release] required command not found: $name" >&2
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
ensure_clean_worktree() {
|
|
15
|
+
if [[ -n "$(git status --porcelain)" ]]; then
|
|
16
|
+
echo "[release] worktree is not clean. Commit/stash first." >&2
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
ensure_on_main() {
|
|
22
|
+
local current_branch
|
|
23
|
+
current_branch="$(git rev-parse --abbrev-ref HEAD)"
|
|
24
|
+
if [[ "$current_branch" != "main" ]]; then
|
|
25
|
+
echo "[release] current branch is \"$current_branch\". Switch to main first." >&2
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
main() {
|
|
31
|
+
require_command git
|
|
32
|
+
require_command npm
|
|
33
|
+
require_command gh
|
|
34
|
+
|
|
35
|
+
cd "$APP_ROOT"
|
|
36
|
+
|
|
37
|
+
if ! gh auth status >/dev/null 2>&1; then
|
|
38
|
+
echo "[release] gh is not authenticated. Run: gh auth login" >&2
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
ensure_clean_worktree
|
|
43
|
+
ensure_on_main
|
|
44
|
+
|
|
45
|
+
echo "[release] syncing local main..."
|
|
46
|
+
git fetch origin main
|
|
47
|
+
git pull --ff-only origin main
|
|
48
|
+
|
|
49
|
+
echo "[release] running package refresh..."
|
|
50
|
+
npm run jskit:update
|
|
51
|
+
|
|
52
|
+
if [[ -z "$(git status --porcelain)" ]]; then
|
|
53
|
+
echo "[release] no changes produced by jskit:update. Nothing to release."
|
|
54
|
+
exit 0
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
local timestamp
|
|
58
|
+
local pretty_time
|
|
59
|
+
local branch
|
|
60
|
+
local commit_message
|
|
61
|
+
local pr_title
|
|
62
|
+
local pr_url
|
|
63
|
+
|
|
64
|
+
timestamp="$(date -u +%Y%m%d-%H%M%S)"
|
|
65
|
+
pretty_time="$(date -u +'%Y-%m-%d %H:%M UTC')"
|
|
66
|
+
branch="release/${timestamp}"
|
|
67
|
+
commit_message="chore: release ${pretty_time}"
|
|
68
|
+
pr_title="Release ${pretty_time}"
|
|
69
|
+
|
|
70
|
+
echo "[release] creating branch ${branch}..."
|
|
71
|
+
git checkout -b "$branch"
|
|
72
|
+
|
|
73
|
+
git add -A
|
|
74
|
+
git commit -m "$commit_message"
|
|
75
|
+
git push -u origin "$branch"
|
|
76
|
+
|
|
77
|
+
pr_url="$(
|
|
78
|
+
gh pr create \
|
|
79
|
+
--base main \
|
|
80
|
+
--head "$branch" \
|
|
81
|
+
--title "$pr_title" \
|
|
82
|
+
--body "Automated release commit generated by \`npm run release\`."
|
|
83
|
+
)"
|
|
84
|
+
|
|
85
|
+
echo "[release] created PR: $pr_url"
|
|
86
|
+
gh pr merge "$pr_url" --merge --delete-branch
|
|
87
|
+
|
|
88
|
+
echo "[release] merged. syncing local main..."
|
|
89
|
+
git checkout main
|
|
90
|
+
git pull --ff-only origin main
|
|
91
|
+
|
|
92
|
+
echo "[release] done."
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
main "$@"
|
|
@@ -10,9 +10,11 @@ import {
|
|
|
10
10
|
resolveRuntimeProfileFromSurface,
|
|
11
11
|
tryCreateProviderRuntimeFromApp
|
|
12
12
|
} from "@jskit-ai/kernel/server/platform";
|
|
13
|
+
import { matchesPathPrefix, normalizePathname } from "@jskit-ai/kernel/shared/surface/paths";
|
|
13
14
|
import { surfaceRuntime } from "./server/lib/surfaceRuntime.js";
|
|
14
15
|
|
|
15
16
|
const SPA_INDEX_FILE = "index.html";
|
|
17
|
+
const API_BASE_PATH = "/api";
|
|
16
18
|
const STATIC_GLOBAL_UI_PATHS = Object.freeze([
|
|
17
19
|
"/assets",
|
|
18
20
|
"/favicon.svg",
|
|
@@ -24,19 +26,18 @@ const STATIC_GLOBAL_UI_PATHS = Object.freeze([
|
|
|
24
26
|
function toRequestPathname(urlValue) {
|
|
25
27
|
const rawUrl = String(urlValue || "").trim() || "/";
|
|
26
28
|
try {
|
|
27
|
-
return new URL(rawUrl, "http://localhost").pathname || "/";
|
|
29
|
+
return normalizePathname(new URL(rawUrl, "http://localhost").pathname || "/");
|
|
28
30
|
} catch {
|
|
29
|
-
return rawUrl.split("?")[0] || "/";
|
|
31
|
+
return normalizePathname(rawUrl.split("?")[0] || "/");
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
function isApiPath(pathname) {
|
|
34
|
-
|
|
35
|
-
return normalizedPathname === "/api" || normalizedPathname.startsWith("/api/");
|
|
36
|
+
return matchesPathPrefix(pathname, API_BASE_PATH);
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
function hasFileExtension(pathname) {
|
|
39
|
-
return path.extname(
|
|
40
|
+
return path.extname(normalizePathname(pathname)) !== "";
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
function resolveGlobalUiPaths(runtimeGlobalUiPaths = []) {
|
|
@@ -48,10 +49,7 @@ function resolveGlobalUiPaths(runtimeGlobalUiPaths = []) {
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
function resolveStaticFilePath(pathname) {
|
|
51
|
-
const normalizedPathname =
|
|
52
|
-
if (!normalizedPathname.startsWith("/")) {
|
|
53
|
-
return "";
|
|
54
|
-
}
|
|
52
|
+
const normalizedPathname = normalizePathname(pathname);
|
|
55
53
|
|
|
56
54
|
const relativePath = normalizedPathname.replace(/^\/+/, "");
|
|
57
55
|
if (!relativePath || relativePath.endsWith("/")) {
|