jops-core 1.0.14 → 1.1.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.
- package/README.md +346 -28
- package/bin/init.js +214 -21
- package/lib/jops-core.d.ts +251 -15
- package/lib/jops-core.min.js +1 -1
- package/lib/nextjs/JopsApp.d.ts +35 -0
- package/lib/nextjs/JopsApp.js +50 -0
- package/package.json +33 -5
- package/templates/nextjs-jops-spa/README.md +34 -0
- package/templates/nextjs-jops-spa/data/quotes.json +62 -0
- package/templates/nextjs-jops-spa/gitignore +21 -0
- package/templates/nextjs-jops-spa/next.config.ts +15 -0
- package/templates/nextjs-jops-spa/package.json +25 -0
- package/templates/nextjs-jops-spa/public/jops-spa/App.js +10 -0
- package/templates/nextjs-jops-spa/public/jops-spa/Login.js +26 -0
- package/templates/nextjs-jops-spa/public/jops-spa/Quotes.js +102 -0
- package/templates/nextjs-jops-spa/public/jops-spa/api.js +15 -0
- package/templates/nextjs-jops-spa/public/jops-spa/badge.js +8 -0
- package/templates/nextjs-jops-spa/public/jops-spa/format.js +5 -0
- package/templates/nextjs-jops-spa/public/jops-spa/layout/login.html +34 -0
- package/templates/nextjs-jops-spa/public/jops-spa/layout/main.html +17 -0
- package/templates/nextjs-jops-spa/public/jops-spa/layout/quotes.html +50 -0
- package/templates/nextjs-jops-spa/public/jops-spa/layout/quotes_row.html +8 -0
- package/templates/nextjs-jops-spa/public/jops-spa/skeleton.js +7 -0
- package/templates/nextjs-jops-spa/src/app/about/page.tsx +59 -0
- package/templates/nextjs-jops-spa/src/app/api/quotes/route.ts +11 -0
- package/templates/nextjs-jops-spa/src/app/globals.css +6 -0
- package/templates/nextjs-jops-spa/src/app/jops-spa/page.tsx +11 -0
- package/templates/nextjs-jops-spa/src/app/layout.tsx +27 -0
- package/templates/nextjs-jops-spa/src/app/page.tsx +5 -0
- package/templates/nextjs-jops-spa/tsconfig.json +21 -0
package/bin/init.js
CHANGED
|
@@ -1,23 +1,79 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from "fs";
|
|
3
|
+
import https from "https";
|
|
3
4
|
import path from "path";
|
|
4
5
|
import readline from "readline";
|
|
6
|
+
import { spawnSync } from "child_process";
|
|
5
7
|
import { fileURLToPath } from "url";
|
|
6
8
|
|
|
7
9
|
const [,, command, ...flags] = process.argv;
|
|
8
10
|
|
|
9
11
|
if (command !== "init" && command !== "build" && command !== "update") {
|
|
10
12
|
console.log("Usage:");
|
|
11
|
-
console.log(" npx jops-core init — vendor library and configure index.html");
|
|
12
|
-
console.log(" npx jops-core update — copy the latest jops-core.min.js to lib/");
|
|
13
|
+
console.log(" npx jops-core init — vendor library and configure index.html, or create a template project");
|
|
14
|
+
console.log(" npx jops-core update — copy the latest jops-core.min.js to lib/ (public/lib/ in a Next.js project)");
|
|
13
15
|
console.log(" npx jops-core build — generate production-ready dist/ folder");
|
|
14
16
|
console.log("");
|
|
15
17
|
console.log("Options (init, update):");
|
|
16
18
|
console.log(" --cordova | --capacitor | --web override the detected project type");
|
|
19
|
+
console.log("Options (all commands):");
|
|
20
|
+
console.log(" --no-update-check skip the check for a newer jops-core version");
|
|
17
21
|
process.exit(0);
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
25
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "../package.json"), "utf8"));
|
|
26
|
+
|
|
27
|
+
// ─── update check ────────────────────────────────────────────────────────────
|
|
28
|
+
// Started now, awaited at the end, so it runs in parallel with the command.
|
|
29
|
+
// Any failure (offline, timeout, Node without fetch) prints nothing.
|
|
30
|
+
|
|
31
|
+
const skipUpdateCheck =
|
|
32
|
+
process.env.CI ||
|
|
33
|
+
process.env.JOPS_NO_UPDATE_CHECK ||
|
|
34
|
+
process.env.NO_UPDATE_NOTIFIER ||
|
|
35
|
+
flags.includes("--no-update-check");
|
|
36
|
+
|
|
37
|
+
// https.get rather than fetch: destroying the request closes the socket at once,
|
|
38
|
+
// whereas an aborted fetch keeps the process alive until its 10 s connect timeout.
|
|
39
|
+
function fetchLatestVersion() {
|
|
40
|
+
return new Promise((resolve) => {
|
|
41
|
+
const req = https.get("https://registry.npmjs.org/jops-core/latest", (res) => {
|
|
42
|
+
if (res.statusCode !== 200) {
|
|
43
|
+
clearTimeout(timer);
|
|
44
|
+
res.resume();
|
|
45
|
+
return resolve(null);
|
|
46
|
+
}
|
|
47
|
+
let body = "";
|
|
48
|
+
res.setEncoding("utf8");
|
|
49
|
+
res.on("data", (chunk) => (body += chunk));
|
|
50
|
+
res.on("end", () => {
|
|
51
|
+
clearTimeout(timer);
|
|
52
|
+
try {
|
|
53
|
+
resolve(JSON.parse(body).version ?? null);
|
|
54
|
+
} catch {
|
|
55
|
+
resolve(null);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
const timer = setTimeout(() => req.destroy(), 1500);
|
|
60
|
+
req.on("error", () => {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
resolve(null);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const latestVersion = skipUpdateCheck ? Promise.resolve(null) : fetchLatestVersion();
|
|
68
|
+
|
|
69
|
+
function isNewer(a, b) {
|
|
70
|
+
const pa = a.split(".").map(Number);
|
|
71
|
+
const pb = b.split(".").map(Number);
|
|
72
|
+
for (let i = 0; i < 3; i++) {
|
|
73
|
+
if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) > (pb[i] || 0);
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
21
77
|
|
|
22
78
|
// ─── project detection ───────────────────────────────────────────────────────
|
|
23
79
|
// projectRoot: package.json, .vscode/ webRoot: index.html, lib/, src/
|
|
@@ -88,6 +144,133 @@ function copyDir(src, dest) {
|
|
|
88
144
|
}
|
|
89
145
|
}
|
|
90
146
|
|
|
147
|
+
// A Next.js project: the library is served from public/lib/.
|
|
148
|
+
function isNextProject(dir) {
|
|
149
|
+
return ["next.config.ts", "next.config.js", "next.config.mjs"].some((f) => fs.existsSync(path.join(dir, f)));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Copies a template project into dest (README.md included, unlike copyDir()).
|
|
153
|
+
// package.json is merged separately; existing files are never overwritten.
|
|
154
|
+
// npm drops .gitignore files from published packages, so the template stores
|
|
155
|
+
// it as "gitignore".
|
|
156
|
+
function copyTemplateProject(src, dest, root = dest) {
|
|
157
|
+
const SKIP = new Set(["node_modules", ".next", ".DS_Store", "package.json"]);
|
|
158
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
159
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
160
|
+
if (dest === root && SKIP.has(entry.name)) continue;
|
|
161
|
+
if (entry.name === ".DS_Store") continue;
|
|
162
|
+
const srcPath = path.join(src, entry.name);
|
|
163
|
+
const destPath = path.join(dest, entry.name === "gitignore" ? ".gitignore" : entry.name);
|
|
164
|
+
if (entry.isDirectory()) copyTemplateProject(srcPath, destPath, root);
|
|
165
|
+
else if (fs.existsSync(destPath)) console.log(`[jops-core] Kept the existing ${path.relative(root, destPath)}`);
|
|
166
|
+
else fs.copyFileSync(srcPath, destPath);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Creates the template's package.json, or merges its dependencies and scripts
|
|
171
|
+
// into an existing one: existing entries (e.g. an installed jops-core) are kept.
|
|
172
|
+
function mergeTemplatePackageJson(templatePath, targetPath) {
|
|
173
|
+
const template = JSON.parse(fs.readFileSync(templatePath, "utf8"));
|
|
174
|
+
if (!fs.existsSync(targetPath)) {
|
|
175
|
+
fs.writeFileSync(targetPath, JSON.stringify(template, null, 2) + "\n", "utf8");
|
|
176
|
+
console.log("[jops-core] Created package.json");
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const pkg = JSON.parse(fs.readFileSync(targetPath, "utf8"));
|
|
180
|
+
pkg.private ??= true;
|
|
181
|
+
// `npm init -y` writes "type": "commonjs", which makes Next.js (Turbopack)
|
|
182
|
+
// reject the project's ES module sources; Next.js projects have no "type".
|
|
183
|
+
if (pkg.type === "commonjs") {
|
|
184
|
+
delete pkg.type;
|
|
185
|
+
console.log('[jops-core] Removed "type": "commonjs" from package.json (Next.js uses ES modules)');
|
|
186
|
+
}
|
|
187
|
+
for (const section of ["dependencies", "devDependencies"]) {
|
|
188
|
+
for (const [name, spec] of Object.entries(template[section] ?? {})) {
|
|
189
|
+
const existing = pkg.dependencies?.[name] ?? pkg.devDependencies?.[name];
|
|
190
|
+
if (existing) {
|
|
191
|
+
if (name === "jops-core") console.log(`[jops-core] Kept the installed jops-core (${existing})`);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
pkg[section] = { ...pkg[section], [name]: spec };
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
for (const [name, command] of Object.entries(template.scripts ?? {})) {
|
|
198
|
+
const existing = pkg.scripts?.[name];
|
|
199
|
+
if (existing && existing !== command) {
|
|
200
|
+
console.log(`[jops-core] Kept the existing "${name}" script (${existing}); the template uses "${command}"`);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
pkg.scripts = { ...pkg.scripts, [name]: command };
|
|
204
|
+
}
|
|
205
|
+
fs.writeFileSync(targetPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
206
|
+
console.log("[jops-core] Added the Next.js dependencies and scripts to package.json");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ─── template selection ──────────────────────────────────────────────────────
|
|
210
|
+
// Asked before init writes anything, because the Next.js template creates its
|
|
211
|
+
// own project folder instead of configuring the current one.
|
|
212
|
+
|
|
213
|
+
async function askTemplate() {
|
|
214
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
215
|
+
const answer = await new Promise((resolve) => rl.question(
|
|
216
|
+
"\nCreate JOPS project from existing templates:\n" +
|
|
217
|
+
" 1. Empty JOPS project (basic skeleton for JOPS project architecture)\n" +
|
|
218
|
+
" 2. Hello World JOPS project (an example project utilizing all features)\n" +
|
|
219
|
+
" 3. Next.js JOPS Embedded SPA (a Next.js project hosting a JOPS SPA)\n" +
|
|
220
|
+
" 4. None (I'll create my own architecture)\n" +
|
|
221
|
+
"Enter choice (1-4): ",
|
|
222
|
+
resolve,
|
|
223
|
+
));
|
|
224
|
+
rl.close();
|
|
225
|
+
return answer.trim();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const choice = command === "init" ? await askTemplate() : null;
|
|
229
|
+
|
|
230
|
+
// ─── init: Next.js JOPS Embedded SPA (template 3) ────────────────────────────
|
|
231
|
+
|
|
232
|
+
if (command === "init" && choice === "3") {
|
|
233
|
+
const cwd = process.cwd();
|
|
234
|
+
const templateDir = path.join(__dirname, "../templates/nextjs-jops-spa");
|
|
235
|
+
// Paths that would clash with an existing project: nothing is written then.
|
|
236
|
+
const conflicts = [
|
|
237
|
+
"next.config.ts", "next.config.js", "next.config.mjs",
|
|
238
|
+
"tsconfig.json", "src/app", "public/jops-spa", "data/quotes.json",
|
|
239
|
+
].filter((p) => fs.existsSync(path.join(cwd, p)));
|
|
240
|
+
|
|
241
|
+
if (isHybrid) {
|
|
242
|
+
console.error(`[jops-core] This is a ${type} project; create the Next.js project in another folder.`);
|
|
243
|
+
process.exitCode = 1;
|
|
244
|
+
} else if (conflicts.length) {
|
|
245
|
+
console.error(`[jops-core] This folder already has ${conflicts.join(", ")}. Nothing was changed.`);
|
|
246
|
+
console.error(" Run init in an empty folder (or a fresh npm project) to create the Next.js project.");
|
|
247
|
+
process.exitCode = 1;
|
|
248
|
+
} else {
|
|
249
|
+
copyTemplateProject(templateDir, cwd);
|
|
250
|
+
mergeTemplatePackageJson(path.join(templateDir, "package.json"), path.join(cwd, "package.json"));
|
|
251
|
+
console.log("[jops-core] Created the Next.js JOPS Embedded SPA project in this folder.");
|
|
252
|
+
|
|
253
|
+
const libDir = path.join(cwd, "public", "lib");
|
|
254
|
+
fs.mkdirSync(libDir, { recursive: true });
|
|
255
|
+
fs.copyFileSync(path.join(__dirname, "../lib/jops-core.min.js"), path.join(libDir, "jops-core.min.js"));
|
|
256
|
+
console.log("[jops-core] Vendored jops-core.min.js → public/lib/jops-core.min.js");
|
|
257
|
+
|
|
258
|
+
console.log("[jops-core] Installing dependencies (npm install)…");
|
|
259
|
+
const install = spawnSync("npm", ["install"], {
|
|
260
|
+
cwd,
|
|
261
|
+
stdio: "inherit",
|
|
262
|
+
shell: process.platform === "win32",
|
|
263
|
+
});
|
|
264
|
+
if (install.status !== 0) {
|
|
265
|
+
console.warn("[jops-core] npm install did not complete. Run it yourself: npm install");
|
|
266
|
+
}
|
|
267
|
+
console.log("");
|
|
268
|
+
console.log("[jops-core] Next steps:");
|
|
269
|
+
console.log(" npm run dev");
|
|
270
|
+
console.log(" open http://localhost:3000/about and http://localhost:3000/jops-spa");
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
91
274
|
// ─── index.html helpers (hybrid projects) ────────────────────────────────────
|
|
92
275
|
|
|
93
276
|
// Same policy as Cordova's default, plus 'unsafe-inline' for the inline import map
|
|
@@ -187,7 +370,7 @@ function schemeWarnings(xml) {
|
|
|
187
370
|
|
|
188
371
|
// ─── init ────────────────────────────────────────────────────────────────────
|
|
189
372
|
|
|
190
|
-
if (command === "init") {
|
|
373
|
+
if (command === "init" && choice !== "3") {
|
|
191
374
|
fs.mkdirSync(webRoot, { recursive: true });
|
|
192
375
|
if (isHybrid) {
|
|
193
376
|
console.log(`[jops-core] ${forced ? "Using" : "Detected"} ${type} project → web folder: ${rel(webRoot)}/`);
|
|
@@ -350,22 +533,10 @@ ${cordovaBody}${MODULE_SCRIPT}
|
|
|
350
533
|
if (changed) fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
351
534
|
}
|
|
352
535
|
|
|
353
|
-
// Template
|
|
354
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
355
|
-
const ask = (q) => new Promise(resolve => rl.question(q, resolve));
|
|
356
|
-
|
|
357
|
-
const choice = await ask(
|
|
358
|
-
"\nCreate JOPS project from existing templates:\n" +
|
|
359
|
-
" 1. Empty JOPS project (basic skeleton for JOPS project architecture)\n" +
|
|
360
|
-
" 2. Hello World JOPS project (an example project utilizing all features)\n" +
|
|
361
|
-
" 3. None (I'll create my own architecture)\n" +
|
|
362
|
-
"Enter choice (1-3): "
|
|
363
|
-
);
|
|
364
|
-
rl.close();
|
|
365
|
-
|
|
536
|
+
// Template choice: asked at the start (see askTemplate()).
|
|
366
537
|
// Cordova's sample page (css/index.css, js/index.js, #deviceready) conflicts with a JOPS
|
|
367
538
|
// template: back it up and start from the JOPS Cordova index.html instead.
|
|
368
|
-
if (type === "cordova" && ["1", "2"].includes(choice
|
|
539
|
+
if (type === "cordova" && ["1", "2"].includes(choice) &&
|
|
369
540
|
fs.readFileSync(indexPath, "utf8").includes('id="deviceready"')) {
|
|
370
541
|
let backup = path.join(webRoot, "index.cordova.html");
|
|
371
542
|
for (let n = 1; fs.existsSync(backup); n++) backup = path.join(webRoot, `index.cordova-${n}.html`);
|
|
@@ -375,7 +546,7 @@ ${cordovaBody}${MODULE_SCRIPT}
|
|
|
375
546
|
}
|
|
376
547
|
|
|
377
548
|
const templateSrc = path.join(__dirname, "../templates");
|
|
378
|
-
if (choice
|
|
549
|
+
if (choice === "1") {
|
|
379
550
|
copyDir(path.join(templateSrc, "empty"), webRoot);
|
|
380
551
|
// Add data-app to the module script in index.html
|
|
381
552
|
let html = fs.readFileSync(indexPath, "utf8");
|
|
@@ -385,7 +556,7 @@ ${cordovaBody}${MODULE_SCRIPT}
|
|
|
385
556
|
);
|
|
386
557
|
fs.writeFileSync(indexPath, html, "utf8");
|
|
387
558
|
console.log("[jops-core] Created empty JOPS project skeleton.");
|
|
388
|
-
} else if (choice
|
|
559
|
+
} else if (choice === "2") {
|
|
389
560
|
copyDir(path.join(templateSrc, "hello-world"), webRoot);
|
|
390
561
|
let html = fs.readFileSync(indexPath, "utf8");
|
|
391
562
|
html = html.replace(
|
|
@@ -402,7 +573,9 @@ ${cordovaBody}${MODULE_SCRIPT}
|
|
|
402
573
|
// ─── update ──────────────────────────────────────────────────────────────────
|
|
403
574
|
|
|
404
575
|
if (command === "update") {
|
|
405
|
-
|
|
576
|
+
// A Next.js project serves the library from public/lib/.
|
|
577
|
+
const libRoot = type === "web" && isNextProject(projectRoot) ? path.join(projectRoot, "public") : webRoot;
|
|
578
|
+
const libDir = path.join(libRoot, "lib");
|
|
406
579
|
const vendorDest = path.join(libDir, "jops-core.min.js");
|
|
407
580
|
const vendorSrc = path.join(__dirname, "../lib/jops-core.min.js");
|
|
408
581
|
|
|
@@ -413,7 +586,6 @@ if (command === "update") {
|
|
|
413
586
|
|
|
414
587
|
fs.copyFileSync(vendorSrc, vendorDest);
|
|
415
588
|
|
|
416
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "../package.json"), "utf8"));
|
|
417
589
|
console.log(`[jops-core] Updated ${rel(vendorDest)} to v${pkg.version}`);
|
|
418
590
|
}
|
|
419
591
|
|
|
@@ -446,8 +618,29 @@ if (command === "build") {
|
|
|
446
618
|
}
|
|
447
619
|
}
|
|
448
620
|
|
|
621
|
+
const vendored = path.join(webRoot, "lib", "jops-core.min.js");
|
|
622
|
+
const installed = path.join(__dirname, "../lib/jops-core.min.js");
|
|
623
|
+
if (fs.existsSync(vendored) && !fs.readFileSync(vendored).equals(fs.readFileSync(installed))) {
|
|
624
|
+
console.warn(`[jops-core] ${rel(vendored)} differs from the installed v${pkg.version} — run \`npx jops-core update\` before building.`);
|
|
625
|
+
}
|
|
626
|
+
|
|
449
627
|
const distDir = path.join(webRoot, "dist");
|
|
450
628
|
if (fs.existsSync(distDir)) fs.rmSync(distDir, { recursive: true, force: true });
|
|
451
629
|
buildCopyDir(webRoot, distDir);
|
|
452
630
|
console.log("[jops-core] Built → dist/");
|
|
453
631
|
}
|
|
632
|
+
|
|
633
|
+
// ─── update notice ───────────────────────────────────────────────────────────
|
|
634
|
+
|
|
635
|
+
const latest = await latestVersion;
|
|
636
|
+
if (latest && isNewer(latest, pkg.version)) {
|
|
637
|
+
console.log("");
|
|
638
|
+
if (command === "update") {
|
|
639
|
+
console.log(`[jops-core] Copied v${pkg.version} from node_modules, but v${latest} is available.`);
|
|
640
|
+
console.log(" Run `npm i jops-core@latest` first, then `npx jops-core update` again.");
|
|
641
|
+
} else {
|
|
642
|
+
console.log(`[jops-core] Update available: ${pkg.version} → ${latest}`);
|
|
643
|
+
console.log(" npm i jops-core@latest && npx jops-core update");
|
|
644
|
+
}
|
|
645
|
+
console.log(" What's new: https://github.com/uidb-dev/jops-core/blob/main/CHANGELOG.md");
|
|
646
|
+
}
|