feedback5: the public dev branch is marked as a development build (the owner's decision of 2026-10-06): version 0.2.0-dev (title footer, boot banner, /healthz), a README warning under the title (unstable, not for public servers, how to report, shared browser storage, no release zips), a visible 开发版 tag on the title screen (zh / en), a boot-banner warning for hosts, and tools/package.mjs refuses a dev version without --allow-dev; test/version.test.js checks all of it
This commit is contained in:
1 parent
6499f4b32d
commit
6b08971203
10 files changed
+65
-15
No files matched your search
@@ -1,8 +1,13 @@
|
||||
# 卫戍协议:盟约 · Stronghold Protocol: Alliance
|
||||
|
||||
> [!WARNING]
|
||||
> **开发版(dev 分支):不稳定,请勿用于公开服务器。** 这里是 0.2.0 的开发中代码,规则、数据和存档格式随时会变,可能有未发现的问题。想玩或开服请使用 [Releases](../../releases/latest) 页面的正式版整合包,或 `master` 分支。反馈问题时请注明「dev」和版本号(标题页底部、启动横幅和 `/healthz` 都会显示 `0.2.0-dev`)。开发版与正式版在同一地址下共用浏览器里保存的设置和登录信息,来回切换后如果界面异常,清除这个网站的数据即可。开发版不提供整合包。
|
||||
>
|
||||
> *Development branch: unstable — do not run it as a public server. Rules, data and saved settings may change at any time. Play or host the latest release (or the `master` branch) instead; when you report a bug from this branch, say "dev" and the version shown on the title screen. It shares the browser's saved settings with a release served at the same address — clear the site's data if the interface misbehaves after switching. No release zips are built from this branch.*
|
||||
|
||||
《明日方舟》季节性自走棋塔防玩法「卫戍协议:盟约」的**非官方同人复刻**:浏览器即开即玩,单人或 1–4 人联机合作。
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
@@ -37,7 +42,7 @@ English summary: [below](#english).
|
||||
|
||||
- **独立模拟**(单人)与**同盟模拟**(1–4 人**合作**,没有 PvP;空位可以加 AI 队友)。
|
||||
- 服务器是一个 Node.js 程序,**战斗在各玩家的浏览器里模拟**(和官方一样),服务器只管经济与回合,一台低功耗小主机就能开服。
|
||||
- 当前版本 0.1.4:修复了 0.1.3 发布后玩家和 GitHub 上反馈的问题,并合并了多位贡献者的 PR,详见 [CHANGELOG.md](CHANGELOG.md)。仍有少数规则按推断实现,与官方不一致的地方欢迎在 Issue 里反馈。
|
||||
- 本分支是 0.2.0 开发版(未发布);最新正式版是 0.1.4,见 [CHANGELOG.md](CHANGELOG.md)。仍有少数规则按推断实现,与官方不一致的地方欢迎在 Issue 里反馈。
|
||||
|
||||
## 功能一览
|
||||
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "stronghold-protocol-alliance",
|
||||
"version": "0.1.4",
|
||||
"version": "0.2.0-dev",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "stronghold-protocol-alliance",
|
||||
"version": "0.1.4",
|
||||
"version": "0.2.0-dev",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "stronghold-protocol-alliance",
|
||||
"version": "0.1.4",
|
||||
"version": "0.2.0-dev",
|
||||
"private": true,
|
||||
"description": "明日方舟「卫戍协议:盟约」非官方同人联机网页复刻(1–4 人合作,Node.js 服务器 + 浏览器客户端)",
|
||||
"license": "GPL-3.0-or-later",
|
||||
|
||||
@@ -261,3 +261,13 @@ html[data-lang="en"] .title-login .btn--xl { letter-spacing: .3em; }
|
||||
font-size: .13rem;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
/* a development build (shared/constants.js DEV_BUILD): an unmissable tag in the footer */
|
||||
.title-dev {
|
||||
padding: .03rem .1rem;
|
||||
border: 1px solid #ff9f43;
|
||||
border-radius: .04rem;
|
||||
color: #ffb066;
|
||||
background: rgba(255, 159, 67, .14);
|
||||
font-weight: 700;
|
||||
letter-spacing: .02em;
|
||||
}
|
||||
+2
-1
@@ -1014,5 +1014,6 @@
|
||||
"还有 {funds} 资金未使用。休整期结束时,本回合的剩余资金将清零。确定准备就绪吗?": "You still have {funds} unspent Funds. The round's leftover Funds are cleared when the Rest Phase ends. Ready anyway?",
|
||||
"选择「{name}」的朝向": "Choose {name}'s facing",
|
||||
"选择朝向": "Choose a facing",
|
||||
"朝向:{dir}": "Facing: {dir}"
|
||||
"朝向:{dir}": "Facing: {dir}",
|
||||
"开发版 · 不稳定,请勿用于公开服务器": "Development build · unstable, not for public servers"
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
// pure CSS/SVG (radar, ridgelines, glow), so it never issues a request that can 404.
|
||||
|
||||
import { useMemo, useState } from '../../vendor/hooks.module.js';
|
||||
import { NAME_MAX_LEN, APP_VERSION } from '../../../shared/constants.js';
|
||||
import { NAME_MAX_LEN, APP_VERSION, DEV_BUILD } from '../../../shared/constants.js';
|
||||
import { html, Button, Icon, MicroLabel, TextField, PingPill } from '../ui/components.js';
|
||||
import { GuideButton } from '../ui/guide.js';
|
||||
import { toast } from '../ui/toasts.js';
|
||||
@@ -280,6 +280,7 @@ export function TitleScreen() {
|
||||
<footer class="title-foot">
|
||||
<span>${t('非官方同人复刻 · 游戏素材版权归 上海鹰角网络 / Yostar 所有')}</span>
|
||||
<${MicroLabel}>v${APP_VERSION} · WEB SIMULATION<//>
|
||||
${DEV_BUILD ? html`<span class="title-dev" role="note">${t('开发版 · 不稳定,请勿用于公开服务器')}</span>` : null}
|
||||
</footer>
|
||||
</div>`;
|
||||
}
|
||||
+2
-1
@@ -10,7 +10,7 @@
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { APP_VERSION } from '../../shared/constants.js';
|
||||
import { APP_VERSION, DEV_BUILD } from '../../shared/constants.js';
|
||||
|
||||
/** Non-internal IPv4 addresses as http URLs. @param {number} port */
|
||||
export function lanUrls(port) {
|
||||
@@ -36,6 +36,7 @@ export function isProcessEntry(metaUrl) {
|
||||
/** Print the boot banner of a started server. @param {{ url: string, host: string, port: number }} srv */
|
||||
export function printBanner(srv) {
|
||||
console.log(`\n 卫戍协议:盟约 · Stronghold Protocol: Alliance v${APP_VERSION}`);
|
||||
if (DEV_BUILD) console.log(' ! 开发版(dev 分支):不稳定,请勿用于公开服务器 · development build — unstable, not for public servers');
|
||||
console.log(` Local: ${srv.url}`);
|
||||
if (srv.host === '0.0.0.0' || srv.host === '::') {
|
||||
for (const u of lanUrls(srv.port)) console.log(` LAN: ${u}`);
|
||||
|
||||
+4
-1
@@ -5,7 +5,10 @@ import { N_ } from './i18n.js';
|
||||
export const PROTOCOL_VERSION = 1;
|
||||
/** Release version shown to players (title screen, server banner, /healthz). Kept equal to package.json "version"
|
||||
* (test/version.test.js); PROTOCOL_VERSION above is the separate wire-format number. */
|
||||
export const APP_VERSION = '0.1.4';
|
||||
export const APP_VERSION = '0.2.0-dev';
|
||||
/** A development build (the public `dev` branch): the title screen, the boot banner and the README say so, and
|
||||
* tools/package.mjs refuses to build a release zip from it without --allow-dev. */
|
||||
export const DEV_BUILD = /-dev$/.test(APP_VERSION);
|
||||
|
||||
export const MAX_SEATS = 4;
|
||||
/**
|
||||
|
||||
+23
-4
@@ -6,7 +6,8 @@ import assert from 'node:assert/strict';
|
||||
import { readFileSync, existsSync } from 'node:fs';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { APP_VERSION, PROTOCOL_VERSION } from '../shared/constants.js';
|
||||
import { APP_VERSION, PROTOCOL_VERSION, DEV_BUILD } from '../shared/constants.js';
|
||||
import { isDevVersion } from '../tools/package.mjs';
|
||||
|
||||
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const read = (p) => readFileSync(join(ROOT, p), 'utf8');
|
||||
@@ -14,7 +15,9 @@ const pkg = JSON.parse(read('package.json'));
|
||||
const lock = JSON.parse(read('package-lock.json'));
|
||||
|
||||
test('one release version: package.json, package-lock.json and APP_VERSION', () => {
|
||||
assert.match(APP_VERSION, /^\d+\.\d+\.\d+$/);
|
||||
// a release x.y.z, or x.y.z-dev on the public dev branch (the owner's decision of 2026-10-06)
|
||||
assert.match(APP_VERSION, /^\d+\.\d+\.\d+(-dev)?$/);
|
||||
assert.equal(DEV_BUILD, APP_VERSION.endsWith('-dev'));
|
||||
assert.equal(pkg.version, APP_VERSION);
|
||||
assert.equal(lock.version, APP_VERSION);
|
||||
assert.equal(lock.packages[''].version, APP_VERSION);
|
||||
@@ -26,11 +29,17 @@ test('CHANGELOG.md opens with the release version, and the README links it', ()
|
||||
const log = read('CHANGELOG.md');
|
||||
const first = log.match(/^## (\d+\.\d+\.\d+) — (\d{4}-\d{2}-\d{2})/m);
|
||||
assert.ok(first, 'a "## x.y.z — date" heading');
|
||||
assert.equal(first[1], APP_VERSION, 'the newest entry is the current version');
|
||||
if (DEV_BUILD) {
|
||||
// a dev build has no entry of its own yet: the newest one is the last release, older than the version in development
|
||||
const num = (v) => v.split(/[.-]/).slice(0, 3).map(Number);
|
||||
const [a, b] = [num(first[1]), num(APP_VERSION)];
|
||||
assert.ok(a[0] < b[0] || (a[0] === b[0] && (a[1] < b[1] || (a[1] === b[1] && a[2] < b[2]))), `the newest entry ${first[1]} precedes ${APP_VERSION}`);
|
||||
} else assert.equal(first[1], APP_VERSION, 'the newest entry is the current version');
|
||||
assert.match(log, /^## 0\.1\.0 — 2026-10-02/m, 'the first public release stays listed');
|
||||
const readme = read('README.md');
|
||||
assert.match(readme, /\[CHANGELOG\.md\]\(CHANGELOG\.md\)/);
|
||||
assert.match(readme, new RegExp(`badge/version-${APP_VERSION.replace(/\./g, '\\.')}-`), 'the README badge');
|
||||
// shields.io escapes a '-' inside a badge field as '--'
|
||||
assert.match(readme, new RegExp(`badge/version-${APP_VERSION.replace(/-/g, '--').replace(/\./g, '\\.')}-`), 'the README badge');
|
||||
});
|
||||
|
||||
test('the release version is what players see', () => {
|
||||
@@ -74,3 +83,13 @@ test('GPL-3.0-or-later: LICENSE, package metadata and notices', () => {
|
||||
assert.match(aklz4, /SPDX-License-Identifier: BSD-3-Clause/);
|
||||
assert.match(aklz4, /Copyright \(c\) 2022, Harry Huang/);
|
||||
});
|
||||
|
||||
test('a development build says so everywhere a player or a host looks, and never packages as a release', () => {
|
||||
if (!DEV_BUILD) return;
|
||||
const readme = read('README.md');
|
||||
assert.match(readme.split('\n').slice(0, 8).join('\n'), /开发版(dev 分支):不稳定,请勿用于公开服务器/, 'the README warns right under the title');
|
||||
assert.match(read('public/js/screens/title.js'), /DEV_BUILD \? html`<span class="title-dev"/, 'the title screen footer carries the dev tag');
|
||||
assert.match(read('server/http/boot.js'), /if \(DEV_BUILD\) console\.log\(/, 'the boot banner warns the host');
|
||||
assert.ok(isDevVersion(APP_VERSION) && !isDevVersion('0.1.4'), 'tools/package.mjs treats the version as a development one');
|
||||
assert.match(read('tools/package.mjs'), /isDevVersion\(p\.version\) && !o\.allowDev/, 'packaging a dev version needs --allow-dev');
|
||||
});
|
||||
+12
-2
@@ -456,10 +456,13 @@ export function build(root, p, { out, install = true, force = false, keepStage =
|
||||
return { zipPath, zipBytes: fileBytes(zipPath), files: staged.length, stage: keepStage ? stage : null };
|
||||
}
|
||||
|
||||
const USAGE = 'usage: node tools/package.mjs [--lite] [--dry-run [--list]] [--out <dir>] [--force] [--keep-stage] [--no-install] [--allow-dirty] [--root <dir>]';
|
||||
const USAGE = 'usage: node tools/package.mjs [--lite] [--dry-run [--list]] [--out <dir>] [--force] [--keep-stage] [--no-install] [--allow-dirty] [--allow-dev] [--root <dir>]';
|
||||
|
||||
/** Whether `version` is a development (pre-release) version, e.g. 0.2.0-dev. */
|
||||
export const isDevVersion = (version) => /-/.test(String(version || ''));
|
||||
|
||||
export function parseArgs(argv) {
|
||||
const o = { lite: false, dryRun: false, list: false, out: '', force: false, keepStage: false, install: true, allowDirty: false, root: REPO, help: false };
|
||||
const o = { lite: false, dryRun: false, list: false, out: '', force: false, keepStage: false, install: true, allowDirty: false, allowDev: false, root: REPO, help: false };
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
const a = argv[i];
|
||||
if (a === '--lite') o.lite = true;
|
||||
@@ -469,6 +472,7 @@ export function parseArgs(argv) {
|
||||
else if (a === '--keep-stage') o.keepStage = true;
|
||||
else if (a === '--no-install') o.install = false;
|
||||
else if (a === '--allow-dirty') o.allowDirty = true;
|
||||
else if (a === '--allow-dev') o.allowDev = true;
|
||||
else if (a === '--help' || a === '-h') o.help = true;
|
||||
else if (a === '--out' || a === '--root') {
|
||||
const v = argv[++i];
|
||||
@@ -491,6 +495,12 @@ function main(argv) {
|
||||
return p.problems.length ? 1 : 0;
|
||||
}
|
||||
if (p.problems.length) return 1;
|
||||
// a development version (0.2.0-dev on the public dev branch) never becomes a release zip by accident: its name carries
|
||||
// the version, and building one needs --allow-dev (a test build, not a release)
|
||||
if (isDevVersion(p.version) && !o.allowDev) {
|
||||
console.error(`package: v${p.version} is a development version — a release zip needs a release version; --allow-dev builds a test zip named after it`);
|
||||
return 1;
|
||||
}
|
||||
try {
|
||||
const r = build(o.root, p, { out: o.out || path.join(os.tmpdir(), 'stronghold-protocol-release'), install: o.install, force: o.force, keepStage: o.keepStage });
|
||||
console.log(`zip: ${r.zipPath} (${MB(r.zipBytes)}, ${r.files} files)${r.stage ? ` · stage kept: ${r.stage}` : ''}`);
|
||||
|
||||
Reference in new issue
Block a user