@bongos/core 1.17.0 → 1.17.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/.bongos-core.json +363 -312
- package/.claude/skills/blocker-review/SKILL.md +1 -1
- package/.claude/skills/builder-claim/SKILL.md +2 -0
- package/.claude/skills/builder-exit/SKILL.md +2 -2
- package/.claude/skills/builder-redteam/SKILL.md +3 -3
- package/.claude/skills/builder-setup/SKILL.md +2 -2
- package/.claude/skills/builder-start/SKILL.md +1 -1
- package/.claude/skills/dev/SKILL.md +1 -1
- package/.claude/skills/idea-triage/SKILL.md +2 -2
- package/.claude/skills/ideate/SKILL.md +1 -1
- package/.claude/skills/merge-mode/SKILL.md +1 -1
- package/.claude/skills/new-project/SKILL.md +3 -0
- package/.claude/skills/paint/SKILL.md +1 -1
- package/CLAUDE.md +11 -238
- package/GOVERNANCE.md +1 -1
- package/README.md +1 -1
- package/bin/bongos.js +1 -0
- package/clients/bongos-client/README.md +4 -4
- package/clients/bongos-client/bongos-client.global.js +14 -1
- package/clients/bongos-client/index.cjs +14 -1
- package/clients/bongos-client/index.d.ts +19 -2
- package/clients/bongos-client/index.mjs +11 -1
- package/clients/bongos-client/package.json +1 -1
- package/docs/adr/0001-stack-choice-single-vps.md +1 -1
- package/docs/adr/0008-google-chat-oauth-user-auth.md +1 -1
- package/docs/adr/0019-red-team-bounty-payout-table.md +3 -3
- package/docs/adr/0020-security-model-public-stub.md +1 -1
- package/docs/adr/0023-kind-multipliers-and-idea-bonus.md +1 -1
- package/docs/adr/0024-cloneable-repo-local-first-memory.md +1 -1
- package/docs/adr/0026-bfg-memory-hygiene-and-cross-builder-learning.md +4 -4
- package/docs/adr/0027-bfg-session-inefficiency-evaluator.md +3 -3
- package/docs/adr/0030-builders-hall-live-updates.md +1 -1
- package/docs/adr/0031-cloud-dev-environments-for-builders.md +4 -4
- package/docs/adr/0032-discord-mirror-of-gds.md +2 -2
- package/docs/adr/0034-thetes-graduated-newcomer-rank.md +1 -1
- package/docs/adr/0044-mediterranean-palette-replacement.md +1 -1
- package/docs/adr/0044-per-box-live-game-preview.md +1 -1
- package/docs/adr/0054-session-token-reward-cost-plus.md +10 -10
- package/docs/adr/0057-container-cost-ledger.md +3 -3
- package/docs/adr/0062-medusa-instance-model-decoupling.md +4 -4
- package/docs/adr/0065-agpl-nonprofit-ai-first-governance.md +2 -2
- package/docs/adr/0085-remove-default-per-builder-spend-cap.md +1 -1
- package/docs/adr/0086-goal-scoped-work-hierarchy.md +1 -1
- package/docs/adr/0096-require-reward-before-workable.md +9 -9
- package/docs/adr/0097-retroactive-reward-backfill.md +3 -3
- package/docs/adr/0100-two-repo-instance-model.md +1 -1
- package/docs/adr/0101-goal-hierarchy-phase-5-judge-membership-reward.md +3 -3
- package/docs/adr/0103-core-first-extraction-cloud-bongos-trunk.md +3 -3
- package/docs/adr/0107-module-upstreaming-contribution-to-core-catalog.md +1 -1
- package/docs/adr/0120-pay-on-land-and-builder-owned-rebase-gate.md +6 -6
- package/docs/adr/0133-github-app-manifest-onboarding-signin.md +2 -0
- package/docs/adr/0134-private-first-npm-distribution.md +20 -6
- package/docs/adr/0135-module-upstream-submission-interim-queue.md +34 -0
- package/docs/adr/0136-update-channel-subscription-policy.md +51 -0
- package/docs/adr/0137-upstream-publish-policy-manual-owner-gated.md +40 -0
- package/docs/adr/0138-bundled-module-deps-declared-per-module.md +45 -0
- package/docs/adr/README.md +14 -10
- package/docs/api/openapi.json +315 -6
- package/docs/api-reference.md +10 -5
- package/docs/architecture.md +3 -3
- package/docs/branding-contract.md +5 -4
- package/docs/canonical-permissions.md +1 -1
- package/docs/design/vanilla-hall-ui-redesign-scope.md +1 -1
- package/docs/file-map.md +20 -19
- package/docs/handoff-template.md +2 -2
- package/docs/modules-contract.md +8 -1
- package/docs/onboarding/diagrams/README.md +7 -7
- package/docs/onboarding/drachmae-and-karma.md +18 -18
- package/docs/onboarding/primer.md +9 -9
- package/docs/recipes/audit-replay.md +2 -2
- package/docs/recipes/builder-box-lifecycle.md +2 -2
- package/docs/recipes/claude-design-loop.md +1 -1
- package/docs/recipes/instance-deploy.md +4 -4
- package/docs/recipes/instance-first-run.md +51 -0
- package/docs/recipes/ops-gotchas.md +1 -1
- package/docs/recipes/packaging-the-core.md +5 -5
- package/docs/recipes/private-npm-distribution.md +39 -12
- package/docs/recipes/self-host.md +1 -1
- package/docs/recipes/staging-environment.md +1 -1
- package/docs/recipes/upgrading-the-core.md +63 -8
- package/docs/research/cursor-competitive-analysis.md +1 -1
- package/docs/research/llm-model-agnostic-and-cheaper-grader.md +1 -1
- package/migrations/core_191_module_submissions.sql +45 -0
- package/modules/autonomy/CLAUDE.md +11 -45
- package/modules/builder-settings/CLAUDE.md +11 -54
- package/modules/dev-box/CLAUDE.md +11 -34
- package/modules/discord/CLAUDE.md +11 -46
- package/modules/discord/module.json +2 -1
- package/modules/economy/CLAUDE.md +11 -44
- package/modules/economy/achievements.js +9 -1
- package/modules/economy/credits.js +10 -1
- package/modules/grading/CLAUDE.md +11 -38
- package/modules/hall-ui/CLAUDE.md +11 -57
- package/modules/hall-ui/public/builders.js +71 -4
- package/modules/hall-ui/public/goal-inbox.js +191 -0
- package/modules/hall-ui/public/goals-page.js +227 -22
- package/modules/hall-ui/public/goals.html +16 -0
- package/modules/hall-ui/public/hall-widgets.js +14 -1
- package/modules/hall-ui/public/index.html +3 -0
- package/modules/hall-ui/public/new-project-wizard.js +6 -4
- package/modules/hall-ui/public/style.css +43 -0
- package/modules/ideas/CLAUDE.md +11 -33
- package/modules/lifecycle/CLAUDE.md +11 -59
- package/modules/lifecycle/routes/goals.js +80 -0
- package/modules/memory/CLAUDE.md +11 -36
- package/modules/onboarding/CLAUDE.md +11 -51
- package/modules/provisioning/CLAUDE.md +15 -0
- package/modules/provisioning/migrations/provisioning_001_tables.sql +160 -0
- package/modules/provisioning/migrations/provisioning_002_standalone_shape.sql +11 -0
- package/modules/provisioning/migrations/provisioning_003_github_app_manifest.sql +95 -0
- package/modules/provisioning/module.json +17 -0
- package/modules/provisioning/onboard-plan.js +37 -0
- package/modules/provisioning/provisioning.js +539 -0
- package/modules/provisioning/rate-limit.js +35 -0
- package/modules/provisioning/routes/provisioning.js +607 -0
- package/modules/provisioning/tests/provisioning.mjs +232 -0
- package/modules/provisioning/ui/.gitkeep +0 -0
- package/modules/security/CLAUDE.md +11 -40
- package/modules/security/secret-scrub.js +10 -0
- package/modules/sessions/CLAUDE.md +11 -35
- package/modules/status-ui/CLAUDE.md +11 -43
- package/package-lock.json +2 -2
- package/package.json +7 -2
- package/public-landing/create.html +58 -12
- package/public-landing/index.html +40 -21
- package/scripts/discord/README.md +1 -1
- package/scripts/gds/CLAUDE.md +11 -140
- package/scripts/gds/claim.js +98 -48
- package/scripts/gds/claude-materialize.js +174 -20
- package/scripts/gds/claude-md-template.js +84 -0
- package/scripts/gds/cli-lib.js +112 -2
- package/scripts/gds/cutover-codemod.js +241 -0
- package/scripts/gds/fitness.js +54 -0
- package/scripts/gds/gen-api-client.js +10 -10
- package/scripts/gds/init.js +112 -16
- package/scripts/gds/lint-infra-exec.js +70 -0
- package/scripts/gds/lint-workflow-exec.js +63 -0
- package/scripts/gds/migration-namespace.js +1 -1
- package/scripts/gds/mirror-lineage-check.js +14 -5
- package/scripts/gds/mirror-redact.js +8 -0
- package/scripts/gds/module.js +118 -4
- package/scripts/gds/npmrc.js +93 -0
- package/scripts/gds/onboard.js +17 -24
- package/scripts/gds/package-core.js +74 -17
- package/scripts/gds/provision.js +102 -17
- package/scripts/gds/publish-manifest.js +60 -2
- package/scripts/gds/run-routine.js +51 -13
- package/scripts/gds/run-unit-tests.js +4 -0
- package/scripts/gds/seed-adopt-tasks.js +1 -1
- package/scripts/gds/status.js +6 -1
- package/scripts/gds/task.js +166 -0
- package/scripts/gds/update-channel.js +186 -0
- package/scripts/gds/upgrade.js +172 -25
- package/src/bongos/CLAUDE.md +11 -60
- package/src/bongos/auth.js +24 -0
- package/src/bongos/module-submissions.js +56 -0
- package/src/bongos/route-rank-check.js +6 -0
- package/src/bongos/routes/CLAUDE.md +11 -39
- package/src/bongos/routes/auth.js +6 -1
- package/src/bongos/routes/modules.js +123 -21
- package/src/bongos/serve-internal.js +10 -1
- package/src/branding.js +1 -0
- package/src/instance-config.js +1 -1
- package/src/module-api.js +24 -1
- package/src/module-loader/loader.js +36 -4
- package/src/module-loader/manifest-schema.js +16 -1
- package/tests/branding.mjs +11 -0
- package/tests/claim_error_surface.mjs +107 -0
- package/tests/claude_materialize.mjs +125 -0
- package/tests/consumer_layout_boot.mjs +302 -0
- package/tests/cutover_codemod.mjs +184 -0
- package/tests/device_flow_start.mjs +96 -0
- package/tests/first_run_engine_check.mjs +79 -0
- package/tests/goal_membership_requests.mjs +33 -3
- package/tests/hook_core_resolve.mjs +125 -0
- package/tests/hook_user_agents.mjs +62 -0
- package/tests/infra_exec_paths.mjs +104 -0
- package/tests/init.mjs +106 -6
- package/tests/instance_mismatch_guard.mjs +101 -0
- package/tests/mirror_lineage_proof.mjs +14 -0
- package/tests/module_cli.mjs +130 -2
- package/tests/module_loader.mjs +42 -0
- package/tests/module_manifest.mjs +19 -0
- package/tests/module_submissions.mjs +135 -0
- package/tests/npmrc.mjs +105 -0
- package/tests/onboard.mjs +62 -0
- package/tests/package_core.mjs +76 -22
- package/tests/platform_boot.mjs +5 -2
- package/tests/provision.mjs +107 -8
- package/tests/provisioning_github_app_routes.mjs +44 -2
- package/tests/publish_manifest.mjs +52 -1
- package/tests/run_routine.mjs +26 -1
- package/tests/run_routine_two_root.mjs +77 -0
- package/tests/scrubber_corpus.mjs +17 -0
- package/tests/served_paths_shipped.mjs +2 -2
- package/tests/task_cli.mjs +99 -0
- package/tests/update_channel.mjs +186 -0
- package/tests/upgrade.mjs +220 -25
- package/tests/workflow_exec_paths.mjs +98 -0
- package/docs/design/container-exterior-tiles.md +0 -49
- package/docs/design/container-exterior.md +0 -81
- package/docs/design/container-interior.md +0 -77
- package/docs/design/world-map-scroll.md +0 -41
- package/docs/recipes/claude-gemini-bongos-doors.md +0 -155
- package/docs/recipes/cloudbongos-staging-gate.md +0 -139
- package/docs/recipes/cloudbongos-standup.md +0 -111
- package/docs/recipes/parallel-load-results-2026-05-30.md +0 -63
- package/docs/recipes/parallel-load-test.md +0 -32
- package/docs/recipes/pixel-art-pipeline.md +0 -411
- package/docs/recipes/staging-data.md +0 -50
- package/migrations/instance/018_pms_v3_planning_seed.sql +0 -131
- package/migrations/instance/080_gds_v4_planning_seed.sql +0 -75
- package/migrations/instance/110_medusa_planning_seed.sql +0 -70
- package/migrations/instance/120_bongos_v1_planning_seed.sql +0 -69
- package/migrations/instance/144_bongos_v1_module_system_criterion.sql +0 -26
- package/migrations/instance/159_bongos_v1_goal_hierarchy_criterion.sql +0 -29
- package/migrations/instance/172_v2_world_planning_seed.sql +0 -163
- package/migrations/instance/173_bongos_v1_c7_core_modularized.sql +0 -33
- package/migrations/instance/182_gdsv4_bongos_consolidation.sql +0 -132
- package/migrations/instance/183_gdsv4_carryover_themed_goals.sql +0 -116
- package/migrations/instance/186_bongos_v1_governance_planning_seed.sql +0 -69
- package/scripts/gds/art-stage.js +0 -152
- package/server.js +0 -212
- package/tests/art_key.mjs +0 -121
- package/tests/day_cycle.mjs +0 -197
- package/tests/snapshot_picker.mjs +0 -233
- package/tests/sprite_facing.mjs +0 -35
- package/tests/terrain_picker.mjs +0 -298
- package/tests/turn_in_place.mjs +0 -280
- package/tests/walkability.mjs +0 -188
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// cutover-codemod.js — rewrite HOST files' core-internal require()s to the @bongos/core
|
|
4
|
+
// package subpaths at cutover (R89 / task 2159 [F1], task 2161 [F3]).
|
|
5
|
+
//
|
|
6
|
+
// WHY. Post-split (task 1728) a host file lives at `<instance>/…` with NO co-located `src/`.
|
|
7
|
+
// Its relative core require()s — `require('../../src/module-api')` (host modules, F1),
|
|
8
|
+
// `require('./src/bongos/…')` (the game entrypoint server.js, F3) — point at the DELETED
|
|
9
|
+
// instance `src/` → boot crash (the 2026-07-10 rehearsal, findings F1+F3:
|
|
10
|
+
// docs/reviews/2026-07-10-amazonprimea-decouple-r89-rehearsal.md). Core modules are UNAFFECTED:
|
|
11
|
+
// they ship INSIDE @bongos/core, where `src/` sits next to `modules/`, so their identical
|
|
12
|
+
// relative requires still resolve. The fix is to rewrite the host files' core requires to the
|
|
13
|
+
// package's EXPORTED subpaths (`@bongos/core/module-api`, `@bongos/core/serve-internal`, …).
|
|
14
|
+
//
|
|
15
|
+
// WHY A CUTOVER TRANSFORM, NOT AN IN-PLACE MONOLITH EDIT. In the monolith `@bongos/core` is not
|
|
16
|
+
// a dependency (the specifier resolves nowhere) and fitness's "modules import only
|
|
17
|
+
// src/module-api.js" check would fail the rewritten form. So this must run AT CUTOVER on the
|
|
18
|
+
// partitioned consumer tree — never on the committed monolith source. Mirrors the `.claude/hooks`
|
|
19
|
+
// `_core-resolve.js` step (task 2139); `claude-materialize.js` rewrites `.claude/`, this rewrites
|
|
20
|
+
// host-module + server.js source — the gap that rehearsal F1/F3 named.
|
|
21
|
+
//
|
|
22
|
+
// TWO GUARDS make it structurally impossible to corrupt the monolith:
|
|
23
|
+
// 1. SCOPE ORACLE = publish-manifest. A file is rewritten only when `isPublishable(rel) === false`
|
|
24
|
+
// (host: game / art-pipeline / character-anim / demo + server.js). Core files (publishable)
|
|
25
|
+
// ship with the package and are left untouched — reusing the exact partition logic as the
|
|
26
|
+
// core-vs-host oracle, so this can never drift from what the cutover actually deletes/keeps.
|
|
27
|
+
// 2. PRECONDITION = post-split consumer. Refuse unless the instance core doorway is gone
|
|
28
|
+
// (`src/module-api.js` absent at the root) — the definitive signal core was partitioned out.
|
|
29
|
+
// In the monolith that file exists, so the codemod refuses. `--force` overrides for a
|
|
30
|
+
// read-only preview only.
|
|
31
|
+
//
|
|
32
|
+
// The rewrite is TABLE-DRIVEN (CORE_INTERNAL_MAP), not a blind `src/X → @bongos/core/X` path swap,
|
|
33
|
+
// because the core's export names do not all mirror the src layout: `src/bongos/serve-internal.js`
|
|
34
|
+
// is exported as `./serve-internal`, not `./bongos/serve-internal`. The table encodes each mapping
|
|
35
|
+
// so a rewrite can never produce a specifier the package doesn't export. Idempotent (a rewritten
|
|
36
|
+
// `@bongos/core/*` require no longer matches the relative-into-src pattern) and quote-preserving.
|
|
37
|
+
|
|
38
|
+
const fs = require('node:fs');
|
|
39
|
+
const path = require('node:path');
|
|
40
|
+
const { execFileSync } = require('node:child_process');
|
|
41
|
+
const { isPublishable } = require('./publish-manifest');
|
|
42
|
+
|
|
43
|
+
const CORE_PKG = '@bongos/core';
|
|
44
|
+
|
|
45
|
+
// <subpath after `src/` in the relative require> → <the @bongos/core export subpath>.
|
|
46
|
+
// The core exports map (scripts/gds/package-core.js buildPackageJson) MUST export every value
|
|
47
|
+
// here, or the rewritten require won't resolve. Kept deliberately small + explicit.
|
|
48
|
+
// F1 (task 2159): the host-module doorway.
|
|
49
|
+
// F3 (task 2161): the six core internals the game entrypoint server.js wires — added there,
|
|
50
|
+
// in lockstep with widening the core exports map, so a rewrite never outruns an export.
|
|
51
|
+
const CORE_INTERNAL_MAP = {
|
|
52
|
+
// F1 — every host module imports the core doorway; the ONLY core import a module is allowed.
|
|
53
|
+
'module-api': 'module-api',
|
|
54
|
+
// F3 (task 2161) — the six core internals the game entrypoint server.js wires. Each LHS is the
|
|
55
|
+
// subpath after `src/` in server.js's relative require; each RHS is the core's EXPORTED subpath
|
|
56
|
+
// (widened in package-core.js buildPackageJson in lockstep). Note the divergence: the file at
|
|
57
|
+
// `src/bongos/serve-internal.js` is exported as `./serve-internal` (a curated name), NOT
|
|
58
|
+
// `./bongos/serve-internal` — this table encodes that so the rewrite can't emit an unexported
|
|
59
|
+
// specifier. The other five mirror their src/ path.
|
|
60
|
+
'bongos/api-path-404': 'bongos/api-path-404',
|
|
61
|
+
'bongos/uptime-poller': 'bongos/uptime-poller',
|
|
62
|
+
'bongos/serve-internal': 'serve-internal',
|
|
63
|
+
'module-loader/loader': 'module-loader/loader',
|
|
64
|
+
'modules': 'modules',
|
|
65
|
+
'module-seams': 'module-seams',
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// require('<./ or ../…>src/<key>') — a RELATIVE require that reaches into a `src/` dir.
|
|
69
|
+
// Group 1 = quote (preserved), group 2 = the `./`/`../…` prefix, group 3 = the key after `src/`.
|
|
70
|
+
// An already-rewritten `require('@bongos/core/…')` has no `../src/` and never matches → idempotent.
|
|
71
|
+
const REQUIRE_RE = /require\((['"])((?:\.\.?\/)+)src\/([^'"]+)\1\)/g;
|
|
72
|
+
|
|
73
|
+
// Pure. Rewrite the core-internal require()s in one file's text per `map`.
|
|
74
|
+
// Returns { text, changes:[{from,to,key}], unmapped:[{key,spec}] }.
|
|
75
|
+
// - changes: requires rewritten to `@bongos/core/<mapped>`.
|
|
76
|
+
// - unmapped: relative-into-`src/` requires whose key is NOT in the table — left UNCHANGED and
|
|
77
|
+
// surfaced so a new core internal a host file starts depending on can't be silently missed
|
|
78
|
+
// (it would still crash at boot; the codemod flags "add an export + a table entry").
|
|
79
|
+
function rewriteCoreRequires(text, map = CORE_INTERNAL_MAP) {
|
|
80
|
+
const changes = [];
|
|
81
|
+
const unmapped = [];
|
|
82
|
+
const out = String(text == null ? '' : text).replace(REQUIRE_RE, (whole, q, _prefix, key) => {
|
|
83
|
+
const mapped = Object.prototype.hasOwnProperty.call(map, key) ? map[key] : null;
|
|
84
|
+
if (mapped == null) {
|
|
85
|
+
unmapped.push({ key, spec: whole });
|
|
86
|
+
return whole; // leave unknown core internals untouched — never guess a specifier
|
|
87
|
+
}
|
|
88
|
+
const to = `require(${q}${CORE_PKG}/${mapped}${q})`;
|
|
89
|
+
changes.push({ from: whole, to, key });
|
|
90
|
+
return to;
|
|
91
|
+
});
|
|
92
|
+
return { text: out, changes, unmapped };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// A file is in scope iff the partition classifies it HOST (not shipped in the core package).
|
|
96
|
+
function isHostFile(rel, isPublishableFn = isPublishable) {
|
|
97
|
+
return isPublishableFn(rel) === false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Post-split consumer signal: the instance's own core doorway is gone. In the monolith
|
|
101
|
+
// `src/module-api.js` exists, so this is false and the CLI refuses (protecting the monolith).
|
|
102
|
+
function looksLikeConsumer(root, { existsFn = fs.existsSync } = {}) {
|
|
103
|
+
return !existsFn(path.join(root, 'src', 'module-api.js'));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Skip vendored deps, VCS metadata, the `.claude/` topology surface (materialize's job), and
|
|
107
|
+
// build output. `dist/` in particular holds an extracted @bongos/core tarball whose bundled
|
|
108
|
+
// core modules carry the SAME `../../src/module-api` requires — scanning it would be noise
|
|
109
|
+
// (it's gitignored throwaway, never part of the consumer).
|
|
110
|
+
const SKIP_DIRS = new Set(['node_modules', '.git', '.claude', 'dist', 'coverage']);
|
|
111
|
+
|
|
112
|
+
// Preferred source scope: git-TRACKED *.js files. A consumer tree is a git repo at cutover, and
|
|
113
|
+
// tracking is the honest "source" oracle — it excludes every gitignored build artifact (dist/,
|
|
114
|
+
// coverage/, …) without enumerating them. Returns repo-relative POSIX paths, or null when git is
|
|
115
|
+
// unavailable / this isn't a repo (→ caller falls back to the filesystem walk).
|
|
116
|
+
function gitTrackedJsFiles(root, { runGit } = {}) {
|
|
117
|
+
const run = runGit || ((args) => execFileSync('git', args, { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }));
|
|
118
|
+
let out;
|
|
119
|
+
try { out = run(['ls-files', '-z', '*.js']); } catch { return null; }
|
|
120
|
+
if (typeof out !== 'string') return null;
|
|
121
|
+
return out.split('\0').filter(Boolean).map((p) => p.split(path.sep).join('/')).sort();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Recursively collect *.js files under `root`, skipping SKIP_DIRS. Returns repo-relative POSIX
|
|
125
|
+
// paths (what isPublishable expects). The fallback when git tracking is unavailable.
|
|
126
|
+
function collectJsFiles(root, { fsImpl = fs } = {}) {
|
|
127
|
+
const found = [];
|
|
128
|
+
const walk = (absDir) => {
|
|
129
|
+
let entries;
|
|
130
|
+
try { entries = fsImpl.readdirSync(absDir, { withFileTypes: true }); } catch { return; }
|
|
131
|
+
for (const ent of entries) {
|
|
132
|
+
if (ent.isDirectory()) {
|
|
133
|
+
if (SKIP_DIRS.has(ent.name)) continue;
|
|
134
|
+
walk(path.join(absDir, ent.name));
|
|
135
|
+
} else if (ent.isFile() && ent.name.endsWith('.js')) {
|
|
136
|
+
found.push(path.relative(root, path.join(absDir, ent.name)).split(path.sep).join('/'));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
walk(root);
|
|
141
|
+
return found.sort();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function underSkipDir(rel) {
|
|
145
|
+
return SKIP_DIRS.has(rel.split('/')[0]);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// The candidate *.js set to consider: git-tracked source when available, else the filesystem walk.
|
|
149
|
+
// SKIP_DIRS is enforced on BOTH paths — git ls-files does not know about it, and `.claude/` in
|
|
150
|
+
// particular is materialize's + _core-resolve.js's domain (task 2139), never the codemod's.
|
|
151
|
+
function candidateJsFiles(root, { fsImpl = fs, runGit } = {}) {
|
|
152
|
+
const tracked = gitTrackedJsFiles(root, { runGit });
|
|
153
|
+
const list = tracked && tracked.length ? tracked : collectJsFiles(root, { fsImpl });
|
|
154
|
+
return list.filter((rel) => !underSkipDir(rel));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Plan the codemod over a consumer `root`: which host files change, and any unmapped core requires.
|
|
158
|
+
// Pure-ish (reads disk via injectable deps for tests). Does NOT write.
|
|
159
|
+
function planCodemod({ root, fsImpl = fs, runGit, isPublishableFn = isPublishable, map = CORE_INTERNAL_MAP } = {}) {
|
|
160
|
+
const files = [];
|
|
161
|
+
const unmapped = [];
|
|
162
|
+
for (const rel of candidateJsFiles(root, { fsImpl, runGit })) {
|
|
163
|
+
if (!isHostFile(rel, isPublishableFn)) continue; // core file → ships with the package, leave it
|
|
164
|
+
let raw;
|
|
165
|
+
try { raw = fsImpl.readFileSync(path.join(root, rel), 'utf8'); } catch { continue; }
|
|
166
|
+
const res = rewriteCoreRequires(raw, map);
|
|
167
|
+
if (res.unmapped.length) for (const u of res.unmapped) unmapped.push({ rel, ...u });
|
|
168
|
+
if (res.changes.length) files.push({ rel, changes: res.changes, newText: res.text });
|
|
169
|
+
}
|
|
170
|
+
return { files, unmapped };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ---- CLI -------------------------------------------------------------------
|
|
174
|
+
function parseArgs(argv) {
|
|
175
|
+
const a = { root: process.cwd(), apply: false, force: false };
|
|
176
|
+
for (let i = 0; i < argv.length; i++) {
|
|
177
|
+
if (argv[i] === '--root') a.root = argv[++i];
|
|
178
|
+
else if (argv[i] === '--apply') a.apply = true;
|
|
179
|
+
else if (argv[i] === '--force') a.force = true;
|
|
180
|
+
}
|
|
181
|
+
return a;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function main(argv = process.argv.slice(2), { log = console.log, err = console.error } = {}) {
|
|
185
|
+
const args = parseArgs(argv);
|
|
186
|
+
const root = path.resolve(args.root);
|
|
187
|
+
|
|
188
|
+
if (!looksLikeConsumer(root) && !args.force) {
|
|
189
|
+
err(`[cutover-codemod] REFUSING: ${path.join(root, 'src/module-api.js')} still exists — this looks like the monolith,`);
|
|
190
|
+
err(' not a post-split consumer. This codemod is a CUTOVER transform and must not edit the monolith');
|
|
191
|
+
err(' (there @bongos/core is not a dep and fitness would reject the rewrite). Run it after the partition,');
|
|
192
|
+
err(' or pass --force for a read-only preview only.');
|
|
193
|
+
return 2;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const plan = planCodemod({ root });
|
|
197
|
+
|
|
198
|
+
for (const u of plan.unmapped) {
|
|
199
|
+
err(`[cutover-codemod] WARNING ${u.rel}: unmapped core-internal require ${u.spec} — add a CORE_INTERNAL_MAP`);
|
|
200
|
+
err(` entry + a matching core export before cutover, or this require will crash at boot.`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (!plan.files.length) {
|
|
204
|
+
log('[cutover-codemod] no host files need rewriting (already converted, or none present).');
|
|
205
|
+
return 0;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const verb = args.apply ? 'rewrote' : 'would rewrite';
|
|
209
|
+
for (const f of plan.files) {
|
|
210
|
+
log(`[cutover-codemod] ${verb} ${f.rel} (${f.changes.length} require${f.changes.length === 1 ? '' : 's'}):`);
|
|
211
|
+
for (const c of f.changes) log(` ${c.from} → ${c.to}`);
|
|
212
|
+
if (args.apply) fs.writeFileSync(path.join(root, f.rel), f.newText);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const total = plan.files.reduce((n, f) => n + f.changes.length, 0);
|
|
216
|
+
if (args.apply) {
|
|
217
|
+
log(`[cutover-codemod] ✓ rewrote ${total} require(s) across ${plan.files.length} host file(s).`);
|
|
218
|
+
} else {
|
|
219
|
+
log(`[cutover-codemod] dry-run: ${total} require(s) across ${plan.files.length} host file(s). Re-run with --apply to write.`);
|
|
220
|
+
}
|
|
221
|
+
return 0;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
module.exports = {
|
|
225
|
+
CORE_PKG,
|
|
226
|
+
CORE_INTERNAL_MAP,
|
|
227
|
+
REQUIRE_RE,
|
|
228
|
+
rewriteCoreRequires,
|
|
229
|
+
isHostFile,
|
|
230
|
+
looksLikeConsumer,
|
|
231
|
+
gitTrackedJsFiles,
|
|
232
|
+
collectJsFiles,
|
|
233
|
+
candidateJsFiles,
|
|
234
|
+
planCodemod,
|
|
235
|
+
parseArgs,
|
|
236
|
+
main,
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
if (require.main === module) {
|
|
240
|
+
process.exit(main());
|
|
241
|
+
}
|
package/scripts/gds/fitness.js
CHANGED
|
@@ -1170,6 +1170,59 @@ function checkCoreRequiresExcludedConfig() {
|
|
|
1170
1170
|
};
|
|
1171
1171
|
}
|
|
1172
1172
|
|
|
1173
|
+
// ============================================================================
|
|
1174
|
+
// Check 16b — core code must not require an instance-EXCLUDED module (task 2135)
|
|
1175
|
+
// ============================================================================
|
|
1176
|
+
//
|
|
1177
|
+
// Sibling of Check 16 for the code→MODULE class. A publishable core file that
|
|
1178
|
+
// require()s a boundary-EXCLUDED module (modules/game, modules/art-pipeline,
|
|
1179
|
+
// modules/character-anim, …) throws MODULE_NOT_FOUND once that module lives in the
|
|
1180
|
+
// consumer's instance repo rather than the core package (R89 split). The cure is
|
|
1181
|
+
// either to reclassify the module INTO core (if it belongs there — e.g. provisioning,
|
|
1182
|
+
// sibling of dev-box) or to exclude the requiring file with the feature it serves
|
|
1183
|
+
// (art tooling). Catches BOTH require forms: a direct literal
|
|
1184
|
+
// require('../modules/<key>/…') AND a require(path.join(<root>, 'modules/<key>/…')).
|
|
1185
|
+
// Only an EXCLUDED module required from a PUBLISHABLE file is a violation; requiring a
|
|
1186
|
+
// publishable (core) module is fine, and the loader's DYNAMIC dispatch
|
|
1187
|
+
// (path.join(dir,'routes',rk) — no literal modules/<key>/ string) is invisible here.
|
|
1188
|
+
const MODULE_PATH_IN_STRING_RE = /['"](?:[^'"]*\/)?modules\/([a-z0-9][a-z0-9-]*)\/[^'"]*['"]/g;
|
|
1189
|
+
|
|
1190
|
+
function checkCoreRequiresExcludedModule() {
|
|
1191
|
+
const { isPublishable } = require('./publish-manifest');
|
|
1192
|
+
const violations = [];
|
|
1193
|
+
const seen = new Set();
|
|
1194
|
+
const allAbs = [
|
|
1195
|
+
...['src', 'scripts'].flatMap((d) => walkJs(path.join(CORE_ROOT, d))),
|
|
1196
|
+
...walkJsAllRoots('modules'),
|
|
1197
|
+
];
|
|
1198
|
+
for (const abs of allAbs) {
|
|
1199
|
+
const relFile = rel(abs);
|
|
1200
|
+
if (seen.has(relFile)) continue;
|
|
1201
|
+
seen.add(relFile);
|
|
1202
|
+
if (!isPublishable(relFile)) continue; // only CORE (publishable) code can crash a vanilla boot
|
|
1203
|
+
for (const raw of fs.readFileSync(abs, 'utf8').split('\n')) {
|
|
1204
|
+
const trimmed = raw.trim();
|
|
1205
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) continue;
|
|
1206
|
+
if (!/require\s*\(/.test(raw)) continue; // only lines that actually require()
|
|
1207
|
+
let m;
|
|
1208
|
+
MODULE_PATH_IN_STRING_RE.lastIndex = 0;
|
|
1209
|
+
while ((m = MODULE_PATH_IN_STRING_RE.exec(raw))) {
|
|
1210
|
+
const key = m[1];
|
|
1211
|
+
if (isPublishable(`modules/${key}/module.json`)) continue; // requiring a CORE module is fine
|
|
1212
|
+
violations.push(`${relFile} → require(...'modules/${key}/…') pulls in an instance-EXCLUDED module — reclassify it into core (if it belongs there) or exclude ${relFile} with the feature it serves, so a fresh core boots (task 2135).`);
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
return {
|
|
1217
|
+
name: 'core never requires an excluded module (task 2135)',
|
|
1218
|
+
ok: violations.length === 0,
|
|
1219
|
+
hardFail: violations.length > 0,
|
|
1220
|
+
violations,
|
|
1221
|
+
warnings: [],
|
|
1222
|
+
note: "a publishable src/·scripts/·modules/ .js must not require('…modules/<key>/…') when that module is boundary-excluded — MODULE_NOT_FOUND post-split (R89).",
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1173
1226
|
// ============================================================================
|
|
1174
1227
|
// Check 17 — no route hand-rolls a FLAT error (BV1 R03 / task 1990 / ADR 0116)
|
|
1175
1228
|
// ============================================================================
|
|
@@ -1360,6 +1413,7 @@ const CHECKS = [
|
|
|
1360
1413
|
checkKernelPromptsNoHardcodedIdentity, // BV1.R76 — kernel prompts read identity from branding()
|
|
1361
1414
|
checkHallSectionRegistry,
|
|
1362
1415
|
checkCoreRequiresExcludedConfig, // task 1824 — core→instance-config require guard
|
|
1416
|
+
checkCoreRequiresExcludedModule, // Check 16b — task 2135: core→excluded-module require guard (R89 split)
|
|
1363
1417
|
checkErrorEnvelope, // Check 17 — BV1 R03 / task 1990: no route hand-rolls a flat error (ADR 0116)
|
|
1364
1418
|
checkWriteRoutesValidated, // Check 18 — BV1 R12 / task 1999: every body-reading write route validates (ADR 0118)
|
|
1365
1419
|
];
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
//
|
|
11
11
|
// Design (mirrors gen-api-docs.js / ADR 0063): deterministic (stable sort, no
|
|
12
12
|
// clock), zero runtime deps, --check gate. The client itself has ZERO deps — it
|
|
13
|
-
// uses the platform `fetch` (Node 18+ / browsers), so `npm i @
|
|
13
|
+
// uses the platform `fetch` (Node 18+ / browsers), so `npm i @bongos/client`
|
|
14
14
|
// pulls nothing transitive.
|
|
15
15
|
//
|
|
16
16
|
// Usage:
|
|
@@ -92,7 +92,7 @@ function renderClientJs(spec, ops) {
|
|
|
92
92
|
const groups = groupByTag(ops);
|
|
93
93
|
const defaultBase = (spec.servers && spec.servers[0] && spec.servers[0].url) || '/api/bongos/v1';
|
|
94
94
|
const L = [];
|
|
95
|
-
L.push(`// @
|
|
95
|
+
L.push(`// @bongos/client — GENERATED from the OpenAPI spec by`);
|
|
96
96
|
L.push(`// scripts/gds/gen-api-client.js (task 1995 / ADR 0118). DO NOT EDIT BY HAND —`);
|
|
97
97
|
L.push(`// regenerate on spec change. Zero deps; uses the platform fetch.`);
|
|
98
98
|
L.push(`'use strict';`);
|
|
@@ -210,7 +210,7 @@ function stripEsmExports(esm) {
|
|
|
210
210
|
// app, which is a zero-dep CJS subproject and vendors this file at package time).
|
|
211
211
|
function renderClientCjs(esm) {
|
|
212
212
|
const header =
|
|
213
|
-
'// @
|
|
213
|
+
'// @bongos/client (CommonJS build) — GENERATED from index.mjs by\n' +
|
|
214
214
|
'// scripts/gds/gen-api-client.js. DO NOT EDIT BY HAND. Same behavior as the ESM\n' +
|
|
215
215
|
'// build; only the module bindings differ. For require()-based consumers.\n';
|
|
216
216
|
const body = stripEsmExports(esm)
|
|
@@ -232,7 +232,7 @@ function renderClientBrowserGlobal(esm) {
|
|
|
232
232
|
.replace(/^'use strict';\n/m, '')
|
|
233
233
|
.replace(/^\/\/ @cloudbongos\/client — GENERATED[^\n]*\n(?:\/\/[^\n]*\n)*/m, '');
|
|
234
234
|
return (
|
|
235
|
-
'// @
|
|
235
|
+
'// @bongos/client (browser-global build) — GENERATED from index.mjs by\n' +
|
|
236
236
|
'// scripts/gds/gen-api-client.js. DO NOT EDIT BY HAND. Exposes window.BongosClient\n' +
|
|
237
237
|
'// for classic <script> pages. Same behavior as the ESM build.\n' +
|
|
238
238
|
'(function (global) {\n' +
|
|
@@ -316,7 +316,7 @@ function methodSignature(op) {
|
|
|
316
316
|
function renderTypes(spec, ops) {
|
|
317
317
|
const groups = groupByTag(ops);
|
|
318
318
|
const L = [];
|
|
319
|
-
L.push(`// GENERATED — TypeScript types for @
|
|
319
|
+
L.push(`// GENERATED — TypeScript types for @bongos/client (task 1995; field-level`);
|
|
320
320
|
L.push(`// request typing added in task 2050 / ADR 0118). DO NOT EDIT BY HAND.`);
|
|
321
321
|
L.push(`export declare const API_VERSION: string;`);
|
|
322
322
|
L.push(`export declare const DEFAULT_BASE_URL: string;`);
|
|
@@ -352,7 +352,7 @@ function renderTypes(spec, ops) {
|
|
|
352
352
|
|
|
353
353
|
function renderPackageJson(spec) {
|
|
354
354
|
return JSON.stringify({
|
|
355
|
-
name: '@
|
|
355
|
+
name: '@bongos/client',
|
|
356
356
|
version: spec.info && spec.info.version ? spec.info.version : '0.0.0',
|
|
357
357
|
description: 'Generated typed client for the Bongos API (ADR 0118). Do not edit by hand.',
|
|
358
358
|
type: 'module',
|
|
@@ -374,7 +374,7 @@ function renderPackageJson(spec) {
|
|
|
374
374
|
license: 'AGPL-3.0-or-later',
|
|
375
375
|
// Pre-launch: publish PRIVATE (task 2089 / ADR 0134). Scoped + access:"restricted"
|
|
376
376
|
// = only paid-org members can install. Flip to "public" (or `npm access public
|
|
377
|
-
// @
|
|
377
|
+
// @bongos/client`) at launch, alongside the core.
|
|
378
378
|
publishConfig: { access: 'restricted' },
|
|
379
379
|
sideEffects: false,
|
|
380
380
|
}, null, 2) + '\n';
|
|
@@ -418,7 +418,7 @@ function exampleLines(ops) {
|
|
|
418
418
|
function renderReadme(spec, ops) {
|
|
419
419
|
const base = (spec.servers && spec.servers[0] && spec.servers[0].url) || '/api/bongos/v1';
|
|
420
420
|
return [
|
|
421
|
-
`# @
|
|
421
|
+
`# @bongos/client`,
|
|
422
422
|
``,
|
|
423
423
|
`A **generated**, zero-dependency typed client for the Bongos API — produced from`,
|
|
424
424
|
`\`docs/api/openapi.json\` by \`scripts/gds/gen-api-client.js\` (ADR 0118). Do not edit`,
|
|
@@ -430,13 +430,13 @@ function renderReadme(spec, ops) {
|
|
|
430
430
|
`## Install`,
|
|
431
431
|
``,
|
|
432
432
|
'```bash',
|
|
433
|
-
`npm install @
|
|
433
|
+
`npm install @bongos/client # internal registry / workspace path`,
|
|
434
434
|
'```',
|
|
435
435
|
``,
|
|
436
436
|
`## Use`,
|
|
437
437
|
``,
|
|
438
438
|
'```js',
|
|
439
|
-
`import { createClient, ApiError } from '@
|
|
439
|
+
`import { createClient, ApiError } from '@bongos/client';`,
|
|
440
440
|
``,
|
|
441
441
|
`const api = createClient({`,
|
|
442
442
|
` baseUrl: 'https://amazonprimea.com${base}', // or omit for same-origin (browser)`,
|
package/scripts/gds/init.js
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
// it refuses where config/branding.json exists, and --force OVERWRITES. `--adopt` is
|
|
38
38
|
// the opposite: it LAYERS Cloud Bongos onto an EXISTING repo (app code, git history,
|
|
39
39
|
// a package.json). It writes the lean config only where absent, adds the pinned
|
|
40
|
-
// @
|
|
40
|
+
// @bongos/core dependency to the owner's package.json without touching any other
|
|
41
41
|
// field, and materializes .claude/ ADDITIVELY — for any pre-existing file it
|
|
42
42
|
// MERGES-or-LEAVES-AND-REPORTS, it NEVER overwrites, and it does not reuse the --force
|
|
43
43
|
// path. It deliberately does NOT seed a synthetic "V1 — Prove the loop": capturing the
|
|
@@ -56,11 +56,17 @@
|
|
|
56
56
|
// bongos init --dir <path> target dir for the config files (default: cwd)
|
|
57
57
|
// bongos init --no-seed write config only; skip the version/task seeding
|
|
58
58
|
// bongos init --force overwrite existing config files (greenfield only; ignored by --adopt)
|
|
59
|
-
// bongos init --vendor-core <tgz> greenfield only: vendor the @
|
|
59
|
+
// bongos init --vendor-core <tgz> greenfield only: vendor the @bongos/core .tgz + pin
|
|
60
60
|
// package.json to file:vendor/… + generate package-lock.json,
|
|
61
61
|
// so the scaffold is `npm ci`-installable (ADR 0108 gap 1/4).
|
|
62
|
-
// Without it the scaffold pins the caret range (
|
|
63
|
-
//
|
|
62
|
+
// Without it the scaffold pins the caret range (which now
|
|
63
|
+
// resolves from the PRIVATE registry — see --registry-core).
|
|
64
|
+
// bongos init --registry-core greenfield only: keep the caret pin + write an env-fed
|
|
65
|
+
// .npmrc (`_authToken=${NPM_TOKEN}`) so `npm ci` pulls the
|
|
66
|
+
// PRIVATE @bongos/core from the npm registry (ADR 0134).
|
|
67
|
+
// With NPM_TOKEN set it also generates package-lock.json;
|
|
68
|
+
// without a token it 404s (the expected tokenless outcome).
|
|
69
|
+
// Mutually exclusive with --vendor-core (registry vs tarball).
|
|
64
70
|
|
|
65
71
|
const fs = require('node:fs');
|
|
66
72
|
const path = require('node:path');
|
|
@@ -72,6 +78,7 @@ const { materializeClaude } = require('./claude-materialize'); // ADR 0108 §3 (
|
|
|
72
78
|
const { detectStack, formatStackSummary } = require('./adopt-detect'); // ADR 0121 §Decision 4 (task 2043): inspect an existing repo before layering
|
|
73
79
|
const { resolveBacklogTasks, DEFAULT_MAX: BACKLOG_MAX } = require('./adopt-import'); // ADR 0121 §Decision 3 (task 2044): import an existing backlog
|
|
74
80
|
const { preflightAdopt, formatPreflight } = require('./adopt-preflight'); // ADR 0121 §Decision 5 (task 2045): the accept-gated conflict pre-flight
|
|
81
|
+
const { writeNpmrc, hasNpmToken } = require('./npmrc'); // ADR 0134 (task 2090): the registry-install path's env-fed .npmrc
|
|
75
82
|
|
|
76
83
|
const KNOWN_MODULES = ['game', 'art-pipeline', 'discord', 'dev-box'];
|
|
77
84
|
|
|
@@ -87,7 +94,7 @@ const HOSTING_SHAPES = ['co-tenant', 'dedicated', 'standalone'];
|
|
|
87
94
|
// `bongos init --adopt` LAYERS this onto an existing repo's package.json; `bongos
|
|
88
95
|
// upgrade` later re-pins it to a vendored `file:` tarball. Named so the adopt
|
|
89
96
|
// layering and the upgrade re-pin agree on the dependency name.
|
|
90
|
-
const CORE_PKG = '@
|
|
97
|
+
const CORE_PKG = '@bongos/core';
|
|
91
98
|
|
|
92
99
|
const isObj = (v) => v && typeof v === 'object' && !Array.isArray(v);
|
|
93
100
|
|
|
@@ -114,19 +121,25 @@ function coreVersionSafe() {
|
|
|
114
121
|
// (`bash <core>/scripts/migrate.sh`). Keeping them identical means `npm start` /
|
|
115
122
|
// `npm run migrate` on the instance behave like production, and `bongos upgrade`'s
|
|
116
123
|
// `npm run migrate` step resolves to the core's migrate.sh (ADR 0108).
|
|
117
|
-
const INSTANCE_CORE_PKG_DIR = 'node_modules/@
|
|
124
|
+
const INSTANCE_CORE_PKG_DIR = 'node_modules/@bongos/core';
|
|
118
125
|
const INSTANCE_START_SCRIPT = `node ${INSTANCE_CORE_PKG_DIR}/src/platform-server.js`;
|
|
119
126
|
const INSTANCE_MIGRATE_SCRIPT = `bash ${INSTANCE_CORE_PKG_DIR}/scripts/migrate.sh`;
|
|
120
127
|
|
|
121
128
|
// PURE (exported for tests) — build/augment the greenfield instance package.json so a
|
|
122
129
|
// fresh instance is `npm ci`-installable and runnable (ADR 0108 greenfield gap 1/4,
|
|
123
|
-
// task 2053): the @
|
|
130
|
+
// task 2053): the @bongos/core dependency (the `file:` tarball pin when a tarball is
|
|
124
131
|
// vendored, else the declared caret range — the CARET RESOLVES NOWHERE on npm, which is
|
|
125
132
|
// exactly why the vendored path pins `file:`) plus the start/migrate npm scripts a
|
|
126
133
|
// standalone instance runs. Preserves any pre-existing fields + scripts (fills only what
|
|
127
134
|
// is missing); greenfield OWNS the core dep, so it is always (re)set to `coreDep`. Never
|
|
128
135
|
// spawns npm — generating the lockfile is a separate imperative step in main().
|
|
129
|
-
|
|
136
|
+
// `moduleDeps` (ADR 0138 / task 2160): the merged external npm deps of the modules this
|
|
137
|
+
// instance ENABLES. @bongos/core ships lean (express+pg, ADR 0108), so a bundled module that
|
|
138
|
+
// needs more (e.g. discord → discord.js) declares it in its module.json `dependencies`, and the
|
|
139
|
+
// consumer's package.json must carry it or boot crashes on the missing module (rehearsal F2).
|
|
140
|
+
// Precedence: module deps FILL gaps, an explicit pre-existing pin WINS over them, @bongos/core
|
|
141
|
+
// is always (re)set. With no moduleDeps this is byte-identical to before.
|
|
142
|
+
function buildInstancePackageJson(existing, { name = 'cloudbongos-instance', coreDep, moduleDeps } = {}) {
|
|
130
143
|
const dep = coreDep || coreDepRange();
|
|
131
144
|
const pkg = isObj(existing) ? { ...existing } : {};
|
|
132
145
|
if (!pkg.name) pkg.name = name;
|
|
@@ -134,10 +147,37 @@ function buildInstancePackageJson(existing, { name = 'cloudbongos-instance', cor
|
|
|
134
147
|
pkg.scripts = { ...(isObj(pkg.scripts) ? pkg.scripts : {}) };
|
|
135
148
|
if (!pkg.scripts.start) pkg.scripts.start = INSTANCE_START_SCRIPT;
|
|
136
149
|
if (!pkg.scripts.migrate) pkg.scripts.migrate = INSTANCE_MIGRATE_SCRIPT;
|
|
137
|
-
pkg.dependencies = {
|
|
150
|
+
pkg.dependencies = {
|
|
151
|
+
...(isObj(moduleDeps) ? moduleDeps : {}),
|
|
152
|
+
...(isObj(pkg.dependencies) ? pkg.dependencies : {}),
|
|
153
|
+
[CORE_PKG]: dep,
|
|
154
|
+
};
|
|
138
155
|
return pkg;
|
|
139
156
|
}
|
|
140
157
|
|
|
158
|
+
// The external npm deps a single module declares in its module.json `dependencies` (ADR 0138).
|
|
159
|
+
// I/O (reads the core-package module manifest); fail-soft → {} when absent/unreadable.
|
|
160
|
+
function moduleDeclaredDeps(moduleKey, { coreRoot = resolveCoreRoot(), fsImpl = fs } = {}) {
|
|
161
|
+
try {
|
|
162
|
+
const mj = JSON.parse(fsImpl.readFileSync(path.join(coreRoot, 'modules', moduleKey, 'module.json'), 'utf8'));
|
|
163
|
+
return isObj(mj.dependencies) ? mj.dependencies : {};
|
|
164
|
+
} catch {
|
|
165
|
+
return {};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Merge the declared deps of every ENABLED module (modulesConfig = { modules: { key: bool } })
|
|
170
|
+
// into one object. PURE — `readDeps(key)` is injected (defaults to moduleDeclaredDeps) so this is
|
|
171
|
+
// testable with no disk. A later-enabled module's range wins on a key collision (rare).
|
|
172
|
+
function enabledModuleDeps(modulesConfig, { readDeps = (k) => moduleDeclaredDeps(k) } = {}) {
|
|
173
|
+
const enabled = isObj(modulesConfig) && isObj(modulesConfig.modules) ? modulesConfig.modules : {};
|
|
174
|
+
const out = {};
|
|
175
|
+
for (const [key, on] of Object.entries(enabled)) {
|
|
176
|
+
if (on === true) Object.assign(out, readDeps(key));
|
|
177
|
+
}
|
|
178
|
+
return out;
|
|
179
|
+
}
|
|
180
|
+
|
|
141
181
|
// ---------------------------------------------------------------------------
|
|
142
182
|
// Pure builders (exported for tests — no I/O, no network)
|
|
143
183
|
// ---------------------------------------------------------------------------
|
|
@@ -258,7 +298,7 @@ function buildHierarchyConfig(spec) {
|
|
|
258
298
|
}
|
|
259
299
|
|
|
260
300
|
// PURE — layer the pinned core dependency into a parsed package.json (ADR 0121
|
|
261
|
-
// §Decision 2 / task 2042). NEVER clobbers: if `@
|
|
301
|
+
// §Decision 2 / task 2042). NEVER clobbers: if `@bongos/core` is already a dep
|
|
262
302
|
// (in dependencies OR devDependencies) it returns the object untouched with
|
|
263
303
|
// action:'present'; if there is no package.json at all it MINTS a minimal one
|
|
264
304
|
// (action:'created'); otherwise it ADDS the dep, preserving every existing field and
|
|
@@ -466,7 +506,7 @@ async function interview({ adopt = false } = {}) {
|
|
|
466
506
|
let provisioning;
|
|
467
507
|
const wantProv = (await ask(rl, ' Provision production hosting now? (y/N)', 'N')).toLowerCase();
|
|
468
508
|
if (wantProv === 'y' || wantProv === 'yes') {
|
|
469
|
-
const shape = (await ask(rl, ' Hosting shape? (co-tenant = share an existing box, $0 / dedicated = its own droplet / standalone = its own repo + pinned @
|
|
509
|
+
const shape = (await ask(rl, ' Hosting shape? (co-tenant = share an existing box, $0 / dedicated = its own droplet / standalone = its own repo + pinned @bongos/core, ADR 0108)', 'co-tenant')).toLowerCase();
|
|
470
510
|
const domain = await ask(rl, ' Domain you own — bring-your-own (blank = none yet)?');
|
|
471
511
|
const targetRef = await ask(rl, ' Target credential ref? (which control-plane creds the runner should use; optional)');
|
|
472
512
|
let tier = '';
|
|
@@ -518,7 +558,11 @@ function writeConfigs(spec, { dir, force, dryRun, coreDep, log = console.log })
|
|
|
518
558
|
const pkgName = slugify((spec.identity && spec.identity.productName) || '', 'cloudbongos-instance');
|
|
519
559
|
let existingPkg = null;
|
|
520
560
|
if (fs.existsSync(pkgPath)) { try { existingPkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); } catch { existingPkg = null; } }
|
|
521
|
-
|
|
561
|
+
// Compose the deps of the ENABLED modules (ADR 0138 / task 2160) so an instance that turns a
|
|
562
|
+
// bundled module on gets that module's npm deps (e.g. discord → discord.js) in its package.json,
|
|
563
|
+
// rather than crashing at boot on a dep @bongos/core deliberately doesn't carry (rehearsal F2).
|
|
564
|
+
const moduleDeps = enabledModuleDeps(modules);
|
|
565
|
+
const pkg = buildInstancePackageJson(existingPkg, { name: pkgName, coreDep, moduleDeps });
|
|
522
566
|
const pinNote = pkg.dependencies[CORE_PKG];
|
|
523
567
|
|
|
524
568
|
if (dryRun) {
|
|
@@ -579,7 +623,7 @@ function layerConfigs(spec, { dir, dryRun, log = console.log }) {
|
|
|
579
623
|
}
|
|
580
624
|
|
|
581
625
|
// `--adopt` package.json layering (ADR 0121 §Decision 2 / task 2042): add the
|
|
582
|
-
// pinned @
|
|
626
|
+
// pinned @bongos/core dependency to the owner's EXISTING package.json without
|
|
583
627
|
// touching any other field, or MINT a minimal one when the repo has none. When the
|
|
584
628
|
// dep is already present the file is left byte-untouched (no reserialize). An
|
|
585
629
|
// unparseable package.json is left alone with a report — adopt never risks
|
|
@@ -785,10 +829,15 @@ async function main(argv) {
|
|
|
785
829
|
const adopt = hasFlag('--adopt', args); // brownfield: LAYER onto an existing repo (ADR 0121)
|
|
786
830
|
const accept = hasFlag('--accept', args) || hasFlag('--yes', args) || hasFlag('-y', args); // adopt pre-flight accept-gate (§Decision 5)
|
|
787
831
|
const vendorCore = arg('--vendor-core', args); // greenfield: vendor + file:-pin a core .tgz, then generate the lockfile (ADR 0108 gap 1/4, task 2053)
|
|
832
|
+
const registryCore = hasFlag('--registry-core', args); // greenfield: caret pin + env-fed .npmrc → `npm ci` pulls the PRIVATE core from the registry (ADR 0134, task 2090)
|
|
788
833
|
|
|
789
834
|
if (adopt && force) {
|
|
790
835
|
console.error('init: --force is a greenfield-only overwrite; --adopt never overwrites, so --force is ignored.');
|
|
791
836
|
}
|
|
837
|
+
if (vendorCore && registryCore) {
|
|
838
|
+
console.error('init: --vendor-core and --registry-core are mutually exclusive — the vendored tarball and the registry pull are two ways to resolve the same core dependency. Pick one.');
|
|
839
|
+
return 1;
|
|
840
|
+
}
|
|
792
841
|
|
|
793
842
|
let raw;
|
|
794
843
|
if (fromPath) {
|
|
@@ -841,7 +890,7 @@ async function main(argv) {
|
|
|
841
890
|
console.log(`\nScaffolding "${spec.identity.productName}"${dryRun ? ' (dry run — nothing will be written or created)' : ''}:`);
|
|
842
891
|
writeConfigs(spec, { dir, force, dryRun, coreDep });
|
|
843
892
|
|
|
844
|
-
// Vendor the pinned @
|
|
893
|
+
// Vendor the pinned @bongos/core tarball + generate the lockfile so the scaffold is
|
|
845
894
|
// `npm ci --omit=dev`-installable by the provisioner (ADR 0108 gap 1/4, task 2053). The
|
|
846
895
|
// `npm install` here is the ONLY heavy step — it CREATES package-lock.json from the file:
|
|
847
896
|
// tarball; the provisioner later runs `npm ci` against that committed lockfile.
|
|
@@ -849,14 +898,42 @@ async function main(argv) {
|
|
|
849
898
|
if (dryRun) {
|
|
850
899
|
console.log(`\n[dry-run] would vendor ${vendorPlan.tgz} → ${vendorPlan.vendorRel} and run \`npm install\` to generate package-lock.json`);
|
|
851
900
|
} else {
|
|
852
|
-
console.log('\nVendoring the pinned @
|
|
901
|
+
console.log('\nVendoring the pinned @bongos/core + generating the lockfile:');
|
|
853
902
|
upgrade.vendorTarball({ instanceDir: dir, fromTgz: vendorPlan.tgz, targetVersion: vendorPlan.version });
|
|
854
903
|
console.log(` ✓ vendored ${vendorPlan.vendorRel}`);
|
|
855
904
|
const inst = upgrade.npmInstall(dir);
|
|
856
905
|
if (!inst.ok) {
|
|
857
906
|
console.error(` ! npm install failed (${inst.reason || 'exit ' + inst.code}) — package-lock.json not generated; run \`npm install\` in ${dir} by hand before provisioning.`);
|
|
858
907
|
} else {
|
|
859
|
-
console.log(' ✓ npm install → package-lock.json generated (node_modules/@
|
|
908
|
+
console.log(' ✓ npm install → package-lock.json generated (node_modules/@bongos/core installed)');
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
// Registry-install path (ADR 0134, task 2090): keep the caret pin writeConfigs wrote
|
|
914
|
+
// and add an env-fed .npmrc so `npm ci` pulls the PRIVATE @bongos/core from the
|
|
915
|
+
// registry. With NPM_TOKEN present we also generate package-lock.json (a reproducible
|
|
916
|
+
// `npm ci` later); without a token we still write the caret pin + .npmrc so the
|
|
917
|
+
// scaffold is registry-shaped, and say plainly that the private package 404s until a
|
|
918
|
+
// token is provided — the expected tokenless outcome the ADR calls to verify.
|
|
919
|
+
if (registryCore) {
|
|
920
|
+
if (dryRun) {
|
|
921
|
+
console.log(`\n[dry-run] would write .npmrc (env-fed @bongos auth) and ${hasNpmToken()
|
|
922
|
+
? 'run `npm install` to generate package-lock.json from the registry'
|
|
923
|
+
: 'SKIP lockfile generation (no NPM_TOKEN — the private core 404s until one is provided)'}`);
|
|
924
|
+
} else {
|
|
925
|
+
console.log('\nWiring the registry-install path (ADR 0134):');
|
|
926
|
+
writeNpmrc(dir, { log: console.log });
|
|
927
|
+
if (hasNpmToken()) {
|
|
928
|
+
const inst = upgrade.npmInstall(dir);
|
|
929
|
+
if (!inst.ok) {
|
|
930
|
+
console.error(` ! npm install failed (${inst.reason || 'exit ' + inst.code}) — confirm NPM_TOKEN reaches the private @bongos org; package-lock.json not generated.`);
|
|
931
|
+
} else {
|
|
932
|
+
console.log(' ✓ npm install → package-lock.json generated (pulled @bongos/core from the registry)');
|
|
933
|
+
}
|
|
934
|
+
} else {
|
|
935
|
+
console.log(' • no NPM_TOKEN in the environment — wrote the caret pin + .npmrc but skipped lockfile generation.');
|
|
936
|
+
console.log(' Provide a read-only automation token (paid `bongos` org) as NPM_TOKEN before `npm ci`; without it the private package 404s cleanly.');
|
|
860
937
|
}
|
|
861
938
|
}
|
|
862
939
|
}
|
|
@@ -868,6 +945,23 @@ async function main(argv) {
|
|
|
868
945
|
console.log('\nMaterializing the .claude/ methodology surface:');
|
|
869
946
|
materializeClaude({ coreRoot: resolveCoreRoot(), instanceDir: dir, dryRun, log: console.log });
|
|
870
947
|
|
|
948
|
+
// Regenerate the per-instance API artifacts (OpenAPI spec + api-reference + typed
|
|
949
|
+
// client) from the PINNED core's routes so this fresh consumer's COMMITTED artifacts
|
|
950
|
+
// match its core from day one — not the core package's build-time snapshot (task 2141).
|
|
951
|
+
// They ship as CORE starters (task 2078 — the hall/docs must render before first regen)
|
|
952
|
+
// yet are per-instance GENERATED; `bongos upgrade` does the same on a bump, this closes
|
|
953
|
+
// the gap at standup. Best-effort: a hiccup warns, never fails init — the instance-root
|
|
954
|
+
// drift guard (fitness `gen-api-docs --check` + the api_client byte-identity test)
|
|
955
|
+
// catches any lag on the consumer's first CI.
|
|
956
|
+
if (dryRun) {
|
|
957
|
+
console.log('\n[dry-run] would regenerate the API artifacts (OpenAPI spec + reference + typed client) from the pinned core');
|
|
958
|
+
} else {
|
|
959
|
+
console.log('\nRegenerating the API artifacts from the pinned core (OpenAPI spec + reference + typed client):');
|
|
960
|
+
const regen = upgrade.regenerateApiArtifacts({ instanceDir: dir, coreRoot: resolveCoreRoot() });
|
|
961
|
+
if (regen.ok) console.log(` ✓ regenerated (${regen.ran.join(', ')}) → docs/api/ + docs/api-reference.md + clients/bongos-client/`);
|
|
962
|
+
else console.error(` ! API-artifact regen skipped (${regen.reason}) — regenerate in ${dir} by hand; the instance-root fitness gate flags the lag otherwise.`);
|
|
963
|
+
}
|
|
964
|
+
|
|
871
965
|
let seedResult = {};
|
|
872
966
|
if (!noSeed) seedResult = (await seedViaApi(spec, { dryRun })) || {};
|
|
873
967
|
else console.log(' (--no-seed: skipped version/task seeding)');
|
|
@@ -893,6 +987,8 @@ module.exports = {
|
|
|
893
987
|
KNOWN_MODULES, HOSTING_SHAPES, CORE_PKG,
|
|
894
988
|
// greenfield installability (ADR 0108 gap 1/4, task 2053)
|
|
895
989
|
buildInstancePackageJson, coreVersionSafe, INSTANCE_START_SCRIPT, INSTANCE_MIGRATE_SCRIPT,
|
|
990
|
+
// enabled-module dep composition (ADR 0138 / task 2160)
|
|
991
|
+
moduleDeclaredDeps, enabledModuleDeps,
|
|
896
992
|
};
|
|
897
993
|
|
|
898
994
|
if (require.main === module) {
|