@celilo/e2e 0.9.2-alpha.0 → 0.9.3
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/config/proxy/squid.conf +7 -2
- package/config/resolver/unbound.conf +13 -3
- package/docker/Dockerfile.minio +1 -1
- package/docker/Dockerfile.target-machine +1 -1
- package/docker/Dockerfile.target-machine-docker +1 -1
- package/docker/Dockerfile.target-technitium +1 -1
- package/package.json +4 -5
- package/scripts/pack-celilo-packages.ts +79 -51
- package/src/cli/build.ts +28 -9
- package/src/cli/command-registry.ts +34 -6
- package/src/cli/index.ts +53 -21
- package/src/container-manager.ts +1 -1
- package/src/runner.ts +128 -57
- package/registry-server/package.json +0 -17
- package/registry-server/src/auth.test.ts +0 -76
- package/registry-server/src/auth.ts +0 -105
- package/registry-server/src/bootstrap-packaging.test.ts +0 -71
- package/registry-server/src/bootstrap.ts +0 -246
- package/registry-server/src/index.ts +0 -16
- package/registry-server/src/landing.ts +0 -90
- package/registry-server/src/rate-limit.test.ts +0 -62
- package/registry-server/src/rate-limit.ts +0 -83
- package/registry-server/src/scoped-token-store.test.ts +0 -93
- package/registry-server/src/scoped-token-store.ts +0 -110
- package/registry-server/src/server.test.ts +0 -509
- package/registry-server/src/server.ts +0 -572
- package/registry-server/src/storage.test.ts +0 -148
- package/registry-server/src/storage.ts +0 -150
- package/registry-server/src/validation.test.ts +0 -86
- package/registry-server/src/validation.ts +0 -60
- package/registry-server/tsconfig.json +0 -15
package/config/proxy/squid.conf
CHANGED
|
@@ -30,8 +30,13 @@ sslcrtd_program /usr/lib/squid/security_file_certgen -s /var/spool/squid/ssl_db
|
|
|
30
30
|
access_log stdio:/var/log/squid/access.log
|
|
31
31
|
cache_log /var/log/squid/cache.log
|
|
32
32
|
|
|
33
|
-
# Resolve via
|
|
34
|
-
|
|
33
|
+
# Resolve via Docker's embedded resolver, which relays to the host's DNS.
|
|
34
|
+
# Same reason as the resolver's forward-zone (config/resolver/unbound.conf):
|
|
35
|
+
# hardcoding public resolvers breaks the harness on any network that blocks
|
|
36
|
+
# direct UDP/53 egress, and squid is what every simulated container's HTTP(S)
|
|
37
|
+
# egress transits — so a squid that can't resolve fails every apt fetch and
|
|
38
|
+
# every `bun add` with "connection refused".
|
|
39
|
+
dns_nameservers 127.0.0.11
|
|
35
40
|
|
|
36
41
|
# Timeouts
|
|
37
42
|
connect_timeout 30 seconds
|
|
@@ -50,8 +50,18 @@ stub-zone:
|
|
|
50
50
|
name: "park-your-domain.com."
|
|
51
51
|
stub-addr: 100.64.0.55
|
|
52
52
|
|
|
53
|
-
# Everything else → real
|
|
53
|
+
# Everything else → the real internet, via Docker's embedded resolver.
|
|
54
|
+
#
|
|
55
|
+
# 127.0.0.11 is Docker's per-container DNS; it relays to whatever the HOST is
|
|
56
|
+
# configured to use. Hardcoding 8.8.8.8/1.1.1.1 here made the whole harness
|
|
57
|
+
# unrunnable on any network that blocks direct UDP/53 to public resolvers —
|
|
58
|
+
# corporate networks and some ISPs force their own resolver, and the symptom is
|
|
59
|
+
# deeply unhelpful: unbound times out, squid's `dns_nameservers` times out with
|
|
60
|
+
# it, and `bun add` reports "ConnectionRefused downloading package manifest
|
|
61
|
+
# drizzle-orm" from inside the bake. Going through the host's resolver works
|
|
62
|
+
# everywhere the host itself has working DNS, which is the actual requirement.
|
|
63
|
+
#
|
|
64
|
+
# `do-not-query-localhost: no` above is what permits this.
|
|
54
65
|
forward-zone:
|
|
55
66
|
name: "."
|
|
56
|
-
forward-addr:
|
|
57
|
-
forward-addr: 1.1.1.1
|
|
67
|
+
forward-addr: 127.0.0.11
|
package/docker/Dockerfile.minio
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# s3.ts) talks plain S3 with forcePathStyle + a custom endpoint, so it drives
|
|
5
5
|
# MinIO unchanged. This sim is the destination for `celilo backup --storage`
|
|
6
6
|
# (push) and the source for `celilo backup pull` (download) in the migration
|
|
7
|
-
# restore e2e (P5,
|
|
7
|
+
# restore e2e (P5, openspec/specs/management-server-backup/spec.md).
|
|
8
8
|
#
|
|
9
9
|
# Placement: internet-external (100.64.0.0/24) like the other public services —
|
|
10
10
|
# the management box reaches it OUTBOUND over its internet egress, exactly the
|
|
@@ -34,7 +34,7 @@ RUN update-ca-certificates
|
|
|
34
34
|
# AND /etc/environment so Ansible's SSH-invoked npm — a session that does NOT
|
|
35
35
|
# inherit Docker ENV — also picks it up via pam_env. Without this, a module's
|
|
36
36
|
# `npm install` against the npm-registry-sim fails SELF_SIGNED_CERT_IN_CHAIN.
|
|
37
|
-
# (
|
|
37
|
+
# (openspec/specs/event-driven-hook-subscriptions/spec.md L6.)
|
|
38
38
|
ENV NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt
|
|
39
39
|
RUN echo 'NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt' >> /etc/environment
|
|
40
40
|
|
|
@@ -38,7 +38,7 @@ RUN update-ca-certificates
|
|
|
38
38
|
# Node/npm ignore the OS trust store; point them at the bundle that now includes
|
|
39
39
|
# the e2e CAs (mirrors Dockerfile.management), in both the image env and
|
|
40
40
|
# /etc/environment so Ansible's SSH-invoked npm picks it up via pam_env.
|
|
41
|
-
# (
|
|
41
|
+
# (openspec/specs/event-driven-hook-subscriptions/spec.md L6.)
|
|
42
42
|
ENV NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt
|
|
43
43
|
RUN echo 'NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt' >> /etc/environment
|
|
44
44
|
|
|
@@ -36,7 +36,7 @@ RUN update-ca-certificates
|
|
|
36
36
|
# Node/npm ignore the OS trust store; point them at the bundle that now includes
|
|
37
37
|
# the e2e CAs (mirrors Dockerfile.management), in both the image env and
|
|
38
38
|
# /etc/environment so Ansible's SSH-invoked npm picks it up via pam_env.
|
|
39
|
-
# (
|
|
39
|
+
# (openspec/specs/event-driven-hook-subscriptions/spec.md L6.)
|
|
40
40
|
ENV NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt
|
|
41
41
|
RUN echo 'NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt' >> /etc/environment
|
|
42
42
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@celilo/e2e",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"description": "E2E test infrastructure for Celilo-deployed applications. Provides a simulated internet with DNS hierarchy, ACME server, firewalls, and target machines in Docker.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"README.md"
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@celilo/cli-display": "
|
|
41
|
-
"@celilo/event-bus": "
|
|
40
|
+
"@celilo/cli-display": "workspace:^",
|
|
41
|
+
"@celilo/event-bus": "workspace:^",
|
|
42
42
|
"yaml": "^2.8.0",
|
|
43
43
|
"zod": "^3.24.1"
|
|
44
44
|
},
|
|
@@ -57,6 +57,5 @@
|
|
|
57
57
|
"type": "git",
|
|
58
58
|
"url": "https://github.com/psbanka/infra",
|
|
59
59
|
"directory": "packages/e2e"
|
|
60
|
-
}
|
|
61
|
-
"gitHead": "8fe2dff30ef6a59a0aed48e5501403e43be8429e"
|
|
60
|
+
}
|
|
62
61
|
}
|
|
@@ -6,21 +6,21 @@
|
|
|
6
6
|
* install.sh runs:
|
|
7
7
|
* bun add -g @celilo/cli @celilo/event-bus @celilo/e2e
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* @celilo/cli-display → (none)
|
|
9
|
+
* Which packages must land in the simulator is the transitive runtime closure
|
|
10
|
+
* of those roots — DERIVED from the live workspace by `installClosure()`, never
|
|
11
|
+
* hand-listed here. A hand-maintained list is exactly what turned this red for
|
|
12
|
+
* days when `@celilo/core` was added as a cli dep and forgotten
|
|
13
|
+
* (apps/celilo/designs/WORKSPACE_PACKAGE_GRAPH.md).
|
|
15
14
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* dependents to an unresolvable old range (ISS-0103: a `0.3→0.4`
|
|
15
|
+
* We rewrite each packed package's `workspace:^` deps to concrete
|
|
16
|
+
* `^<workspace-version>` ranges OURSELVES before `bun pm pack` — we do NOT rely
|
|
17
|
+
* on bun's lock-based expansion, which goes stale after a cross-caret version
|
|
18
|
+
* bump and pins dependents to an unresolvable old range (ISS-0103: a `0.3→0.4`
|
|
21
19
|
* capabilities bump left the lock at 0.3, so packed cli pinned
|
|
22
|
-
* `^0.3.0-alpha.0`, excluding the 0.4.x the sim served).
|
|
23
|
-
*
|
|
20
|
+
* `^0.3.0-alpha.0`, excluding the 0.4.x the sim served). The rewrite map is the
|
|
21
|
+
* FULL workspace version map (every @celilo/* package), so any `workspace:` dep
|
|
22
|
+
* that can't be resolved is a genuine error and throws (see rewriteWorkspacePins).
|
|
23
|
+
* package.json is restored after packing so `workspace:^` stays in source.
|
|
24
24
|
*
|
|
25
25
|
* Output directory is typically `packages/e2e/.npm-registry-cache/` —
|
|
26
26
|
* staged into the npm-registry-sim Docker image at build time.
|
|
@@ -30,11 +30,53 @@
|
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
32
|
import { execSync } from 'node:child_process';
|
|
33
|
-
import { existsSync, mkdirSync,
|
|
34
|
-
import { join } from 'node:path';
|
|
33
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
34
|
+
import { join, resolve } from 'node:path';
|
|
35
|
+
import {
|
|
36
|
+
installClosure,
|
|
37
|
+
readWorkspace,
|
|
38
|
+
workspaceVersionMap,
|
|
39
|
+
} from '../../../scripts/workspace-graph';
|
|
35
40
|
import { ensureRegistryServerBundle } from '../src/registry-bundle';
|
|
36
41
|
|
|
37
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Resolve the celilo monorepo root by walking up from `startDir` (the caller's
|
|
44
|
+
* cwd by default) looking for `apps/celilo/package.json`.
|
|
45
|
+
*
|
|
46
|
+
* This replaces a hardcoded `join(import.meta.dir, '..', '..', '..')`. That
|
|
47
|
+
* derivation assumed the script always lives at `<repo>/packages/e2e/scripts/`,
|
|
48
|
+
* which holds in a checkout and nowhere else: the script ships inside the
|
|
49
|
+
* published `@celilo/e2e` tarball, where `../../..` resolves to the consumer's
|
|
50
|
+
* `node_modules/` and every path built from it points at
|
|
51
|
+
* `node_modules/apps/celilo/...` → ENOENT (ce-dae). `build.ts` already spawns
|
|
52
|
+
* this script with `cwd: <repoRoot>`, so the caller is the single source of
|
|
53
|
+
* truth for where the workspace is — take it from there, don't re-derive it.
|
|
54
|
+
* Walking up (rather than requiring cwd to BE the root exactly) also lets it run
|
|
55
|
+
* from a subdir of the checkout, e.g. `bun test` from `packages/e2e`.
|
|
56
|
+
*/
|
|
57
|
+
export function resolveRepoRoot(startDir: string = process.cwd()): string {
|
|
58
|
+
let dir = startDir;
|
|
59
|
+
for (let i = 0; i < 10; i++) {
|
|
60
|
+
if (existsSync(join(dir, 'apps', 'celilo', 'package.json'))) return dir;
|
|
61
|
+
const parent = resolve(dir, '..');
|
|
62
|
+
if (parent === dir) break;
|
|
63
|
+
dir = parent;
|
|
64
|
+
}
|
|
65
|
+
throw new Error(
|
|
66
|
+
`pack-celilo-packages: no celilo checkout found from '${startDir}' (looked for apps/celilo/package.json walking up). This is a monorepo-internal dev script — run it from a checkout via 'cele2e build-infra'.`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Lazily memoized so importing this module (e.g. the rewriteWorkspacePins unit
|
|
71
|
+
// test) never triggers resolution — only the packing path, which runs inside a
|
|
72
|
+
// checkout, resolves the root.
|
|
73
|
+
let _repoRoot: string | undefined;
|
|
74
|
+
function repoRoot(): string {
|
|
75
|
+
if (_repoRoot === undefined) {
|
|
76
|
+
_repoRoot = resolveRepoRoot();
|
|
77
|
+
}
|
|
78
|
+
return _repoRoot;
|
|
79
|
+
}
|
|
38
80
|
|
|
39
81
|
interface PackageJson {
|
|
40
82
|
name?: string;
|
|
@@ -83,7 +125,14 @@ export function rewriteWorkspacePins(
|
|
|
83
125
|
for (const [name, spec] of Object.entries(deps)) {
|
|
84
126
|
if (!spec.startsWith('workspace:')) continue;
|
|
85
127
|
const version = versions.get(name);
|
|
86
|
-
if (!version)
|
|
128
|
+
if (!version) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`Cannot rewrite ${bucket} "${name}": "${spec}" — ${name} is not a known ` +
|
|
131
|
+
`@celilo/* workspace package. Add it to the workspace, or fix the dep name. ` +
|
|
132
|
+
`(This is the loud failure the old hardcoded pack list lacked — ` +
|
|
133
|
+
`see apps/celilo/designs/WORKSPACE_PACKAGE_GRAPH.md.)`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
87
136
|
const range = spec.slice('workspace:'.length);
|
|
88
137
|
let newSpec: string;
|
|
89
138
|
if (range === '*' || range === '') newSpec = '*';
|
|
@@ -98,27 +147,12 @@ export function rewriteWorkspacePins(
|
|
|
98
147
|
return rewrites;
|
|
99
148
|
}
|
|
100
149
|
|
|
101
|
-
/**
|
|
102
|
-
* Workspace packages that install.sh's transitive @celilo/* closure depends
|
|
103
|
-
* on. Hardcoded by name → relative path so a typo in package.json or a future
|
|
104
|
-
* rename gets caught at build time rather than at test time. If a new
|
|
105
|
-
* @celilo/* workspace package becomes a runtime dep of cli/event-bus/e2e,
|
|
106
|
-
* add it here.
|
|
107
|
-
*/
|
|
108
|
-
const PACKAGES_TO_PACK = [
|
|
109
|
-
{ name: '@celilo/cli', path: 'apps/celilo' },
|
|
110
|
-
{ name: '@celilo/capabilities', path: 'packages/capabilities' },
|
|
111
|
-
{ name: '@celilo/cli-display', path: 'packages/cli-display' },
|
|
112
|
-
{ name: '@celilo/e2e', path: 'packages/e2e' },
|
|
113
|
-
{ name: '@celilo/event-bus', path: 'packages/event-bus' },
|
|
114
|
-
] as const;
|
|
115
|
-
|
|
116
150
|
interface CliArgs {
|
|
117
151
|
destination: string;
|
|
118
152
|
}
|
|
119
153
|
|
|
120
154
|
function parseArgs(): CliArgs {
|
|
121
|
-
const defaultDest = join(
|
|
155
|
+
const defaultDest = join(repoRoot(), 'packages', 'e2e', '.npm-registry-cache');
|
|
122
156
|
let destination = defaultDest;
|
|
123
157
|
for (const arg of process.argv.slice(2)) {
|
|
124
158
|
if (arg.startsWith('--dest=')) {
|
|
@@ -132,7 +166,7 @@ function parseArgs(): CliArgs {
|
|
|
132
166
|
}
|
|
133
167
|
|
|
134
168
|
function pack(pkgPath: string, destination: string, versions: Map<string, string>): string {
|
|
135
|
-
const fullPath = join(
|
|
169
|
+
const fullPath = join(repoRoot(), pkgPath);
|
|
136
170
|
const pkgJsonPath = join(fullPath, 'package.json');
|
|
137
171
|
if (!existsSync(pkgJsonPath)) {
|
|
138
172
|
throw new Error(`No package.json in ${fullPath}`);
|
|
@@ -173,13 +207,6 @@ function pack(pkgPath: string, destination: string, versions: Map<string, string
|
|
|
173
207
|
}
|
|
174
208
|
}
|
|
175
209
|
|
|
176
|
-
function workspaceVersion(pkgPath: string): string {
|
|
177
|
-
const json = JSON.parse(readFileSync(join(REPO_ROOT, pkgPath, 'package.json'), 'utf-8')) as {
|
|
178
|
-
version: string;
|
|
179
|
-
};
|
|
180
|
-
return json.version;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
210
|
function main(): void {
|
|
184
211
|
const { destination } = parseArgs();
|
|
185
212
|
|
|
@@ -195,22 +222,23 @@ function main(): void {
|
|
|
195
222
|
// "published without its registry-server bundle"). Regenerate it from the
|
|
196
223
|
// sibling packages/registry-server before packing, exactly as buildDockerImages
|
|
197
224
|
// does in-monorepo. (This is why npm-consumer-smoke was red — ce-3rs.)
|
|
198
|
-
ensureRegistryServerBundle(join(
|
|
225
|
+
ensureRegistryServerBundle(join(repoRoot(), 'packages', 'e2e'));
|
|
199
226
|
|
|
200
227
|
console.log(`Packing @celilo/* packages → ${destination}`);
|
|
201
228
|
console.log('');
|
|
202
229
|
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
);
|
|
230
|
+
// Derive the package set + version map from the live workspace (never a
|
|
231
|
+
// hardcoded list). The rewrite map is the FULL workspace (every @celilo/*
|
|
232
|
+
// package) so any workspace: dep resolves or throws; the pack set is the
|
|
233
|
+
// runtime install closure of install.sh's roots.
|
|
234
|
+
const workspace = readWorkspace(repoRoot());
|
|
235
|
+
const versions = workspaceVersionMap(workspace);
|
|
236
|
+
const toPack = installClosure(workspace);
|
|
208
237
|
|
|
209
238
|
const results: Array<{ name: string; version: string; tarball: string }> = [];
|
|
210
|
-
for (const { name,
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const tarball = pack(path, destination, versions);
|
|
239
|
+
for (const { name, dir, version } of toPack) {
|
|
240
|
+
console.log(`▸ ${name}@${version} (${dir})`);
|
|
241
|
+
const tarball = pack(dir, destination, versions);
|
|
214
242
|
results.push({ name, version, tarball });
|
|
215
243
|
console.log(` → ${tarball}`);
|
|
216
244
|
console.log('');
|
package/src/cli/build.ts
CHANGED
|
@@ -25,11 +25,22 @@ import { gunzipSync } from 'node:zlib';
|
|
|
25
25
|
import { stageAptRepo } from '../../scripts/stage-apt-repo';
|
|
26
26
|
import { ensureRegistryServerBundle } from '../registry-bundle';
|
|
27
27
|
|
|
28
|
-
/**
|
|
29
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Walk up from pkgDir to find the celilo monorepo root.
|
|
30
|
+
*
|
|
31
|
+
* The marker is `apps/celilo/package.json` — a file that only a real celilo
|
|
32
|
+
* checkout has — NOT the presence of a `modules/` directory. A bare `modules/`
|
|
33
|
+
* is too weak a signal: a stray empty `node_modules/modules` (left by a botched
|
|
34
|
+
* `bun add -g`) made this return the global node_modules as "the monorepo root",
|
|
35
|
+
* which skipped `stageFromPublic()` and crashed the npm-consumer path in the
|
|
36
|
+
* workspace packer (ce-dbc). Matches the marker `container-manager.ts`'s
|
|
37
|
+
* findCeliloRoot already uses. Returns null in any consumer install, so callers
|
|
38
|
+
* correctly fall through to the published-source path.
|
|
39
|
+
*/
|
|
40
|
+
export function findMonorepoRoot(pkgDir: string): string | null {
|
|
30
41
|
let dir = pkgDir;
|
|
31
42
|
for (let i = 0; i < 10; i++) {
|
|
32
|
-
if (existsSync(join(dir, '
|
|
43
|
+
if (existsSync(join(dir, 'apps', 'celilo', 'package.json'))) return dir;
|
|
33
44
|
const parent = resolve(dir, '..');
|
|
34
45
|
if (parent === dir) break;
|
|
35
46
|
dir = parent;
|
|
@@ -171,16 +182,24 @@ async function stageSimulatorInputs(pkgDir: string): Promise<void> {
|
|
|
171
182
|
/**
|
|
172
183
|
* The published @celilo/* packages the npm-registry-sim must serve so
|
|
173
184
|
* install.sh's `bun add -g @celilo/cli @celilo/event-bus @celilo/e2e`
|
|
174
|
-
* resolves its full transitive closure against the sim.
|
|
175
|
-
*
|
|
176
|
-
*
|
|
185
|
+
* resolves its full transitive closure against the sim.
|
|
186
|
+
*
|
|
187
|
+
* This is the ONE place the install closure is a literal rather than derived:
|
|
188
|
+
* build.ts runs consumer-side (inside the published @celilo/e2e tarball, with
|
|
189
|
+
* NO monorepo present), so it cannot read the workspace package.json set at
|
|
190
|
+
* runtime the way the monorepo packer does. `installClosure(readWorkspace())`
|
|
191
|
+
* is the source of truth; this literal must match it, and
|
|
192
|
+
* published-packages-in-sync.test.ts fails the instant they drift (that test is
|
|
193
|
+
* why forgetting a package here can't recur — see WORKSPACE_PACKAGE_GRAPH.md).
|
|
194
|
+
* Keep this sorted the same as installClosure()'s topo output for easy diffing.
|
|
177
195
|
*/
|
|
178
|
-
const PUBLISHED_CELILO_PACKAGES = [
|
|
179
|
-
'@celilo/cli',
|
|
196
|
+
export const PUBLISHED_CELILO_PACKAGES = [
|
|
180
197
|
'@celilo/capabilities',
|
|
181
198
|
'@celilo/cli-display',
|
|
182
|
-
'@celilo/
|
|
199
|
+
'@celilo/core',
|
|
183
200
|
'@celilo/event-bus',
|
|
201
|
+
'@celilo/e2e',
|
|
202
|
+
'@celilo/cli',
|
|
184
203
|
] as const;
|
|
185
204
|
|
|
186
205
|
/**
|
|
@@ -25,16 +25,31 @@ export const COMMANDS: CommandDef[] = [
|
|
|
25
25
|
name: 'run',
|
|
26
26
|
description: 'Run e2e tests — pass a module path or pattern, or --all / --all-modules',
|
|
27
27
|
flags: [
|
|
28
|
-
{
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
{
|
|
29
|
+
name: '--all',
|
|
30
|
+
description:
|
|
31
|
+
'Full regression: every module suite + top-level e2e/tests/ (quarantined cele2e-ci-unsafe tests skipped)',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: '--complete',
|
|
35
|
+
description:
|
|
36
|
+
'Run EVERYTHING including quarantined (cele2e-ci-unsafe) tests — the only mode that opts into them',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: '--ci-safe',
|
|
40
|
+
description:
|
|
41
|
+
'Alias for the default full regression (quarantined tests skipped); kept for the nightly',
|
|
42
|
+
},
|
|
31
43
|
{ name: '--all-modules', description: 'Run e2e tests for all modules with e2e/ directories' },
|
|
32
44
|
{ name: '--keep', description: 'Keep network running after tests' },
|
|
33
45
|
{ name: '--reuse', description: 'Reuse existing network if running' },
|
|
34
46
|
{ name: '--live', description: 'Use live (non-simulated) internet' },
|
|
35
47
|
{ name: '--published', description: 'Use published .netapp packages' },
|
|
36
48
|
{ name: '--notify', description: 'Desktop notification when the run finishes' },
|
|
37
|
-
{
|
|
49
|
+
{
|
|
50
|
+
name: '--shuffle',
|
|
51
|
+
description: 'Randomize test order to surface order-dependent bugs (seed is logged)',
|
|
52
|
+
},
|
|
38
53
|
{ name: '--seed=', description: 'Replay a specific --shuffle order (e.g. --seed=12345)' },
|
|
39
54
|
],
|
|
40
55
|
},
|
|
@@ -65,7 +80,10 @@ export const COMMANDS: CommandDef[] = [
|
|
|
65
80
|
description: 'Rebuild Docker images and package all modules as .netapps',
|
|
66
81
|
flags: [
|
|
67
82
|
{ name: '--save', description: 'Save Docker images to tarball after build' },
|
|
68
|
-
{
|
|
83
|
+
{
|
|
84
|
+
name: '--skip-modules',
|
|
85
|
+
description: 'Skip .netapp packaging (only rebuild Docker images)',
|
|
86
|
+
},
|
|
69
87
|
],
|
|
70
88
|
},
|
|
71
89
|
{
|
|
@@ -109,7 +127,17 @@ export const COMMANDS: CommandDef[] = [
|
|
|
109
127
|
|
|
110
128
|
export const UP_PRESETS = ['--caddy', '--full-stack', '--infrastructure'];
|
|
111
129
|
|
|
112
|
-
export const RUN_FLAGS = [
|
|
130
|
+
export const RUN_FLAGS = [
|
|
131
|
+
'--all',
|
|
132
|
+
'--complete',
|
|
133
|
+
'--ci-safe',
|
|
134
|
+
'--all-modules',
|
|
135
|
+
'--keep',
|
|
136
|
+
'--reuse',
|
|
137
|
+
'--live',
|
|
138
|
+
'--published',
|
|
139
|
+
'--notify',
|
|
140
|
+
];
|
|
113
141
|
|
|
114
142
|
export const DOWN_FLAGS = ['--keep', '--all'];
|
|
115
143
|
|
package/src/cli/index.ts
CHANGED
|
@@ -19,19 +19,27 @@ import { spawnSync } from 'node:child_process';
|
|
|
19
19
|
import { existsSync, readFileSync, readdirSync, rmSync } from 'node:fs';
|
|
20
20
|
import { basename, dirname, join, resolve } from 'node:path';
|
|
21
21
|
import { parse as parseYaml } from 'yaml';
|
|
22
|
+
import {
|
|
23
|
+
E2eBusyError,
|
|
24
|
+
acquireRunLock,
|
|
25
|
+
clearLock,
|
|
26
|
+
formatBusy,
|
|
27
|
+
lockStatus,
|
|
28
|
+
markKept,
|
|
29
|
+
} from '../run-lock';
|
|
22
30
|
import { runBuild } from './build';
|
|
23
|
-
import { runScaffold } from './scaffold';
|
|
24
31
|
import { generateBashCompletion, generateZshCompletion, getCompletions } from './completion';
|
|
25
32
|
import { findAllModules, findModulesWithMatchingTest } from './module-discovery';
|
|
26
|
-
import {
|
|
33
|
+
import { runScaffold } from './scaffold';
|
|
27
34
|
|
|
28
35
|
const PKG_DIR = resolve(import.meta.dir, '../..');
|
|
29
36
|
const BIN_DIR = join(PKG_DIR, 'bin');
|
|
30
37
|
|
|
31
|
-
|
|
32
38
|
function readManifestTestsDir(manifestPath: string): string | undefined {
|
|
33
39
|
try {
|
|
34
|
-
const parsed = parseYaml(readFileSync(manifestPath, 'utf-8')) as {
|
|
40
|
+
const parsed = parseYaml(readFileSync(manifestPath, 'utf-8')) as {
|
|
41
|
+
e2e?: { tests_dir?: string };
|
|
42
|
+
};
|
|
35
43
|
return parsed?.e2e?.tests_dir;
|
|
36
44
|
} catch {
|
|
37
45
|
return undefined;
|
|
@@ -90,14 +98,26 @@ function discoverSuiteTests(suiteRoot: string): { name: string; file: string; gr
|
|
|
90
98
|
? resolve(moduleDir, readManifestTestsDir(join(moduleDir, 'manifest.yml')) ?? 'e2e')
|
|
91
99
|
: tp;
|
|
92
100
|
if (!existsSync(testsDir)) continue;
|
|
93
|
-
for (const f of readdirSync(testsDir)
|
|
94
|
-
|
|
101
|
+
for (const f of readdirSync(testsDir)
|
|
102
|
+
.filter((f) => f.endsWith('.test.ts'))
|
|
103
|
+
.sort()) {
|
|
104
|
+
out.push({
|
|
105
|
+
name: f.replace(/\.test\.ts$/, ''),
|
|
106
|
+
file: join(testsDir, f),
|
|
107
|
+
group: basename(moduleDir),
|
|
108
|
+
});
|
|
95
109
|
}
|
|
96
110
|
}
|
|
97
111
|
const topLevel = join(suiteRoot, 'e2e', 'tests');
|
|
98
112
|
if (existsSync(topLevel)) {
|
|
99
|
-
for (const f of readdirSync(topLevel)
|
|
100
|
-
|
|
113
|
+
for (const f of readdirSync(topLevel)
|
|
114
|
+
.filter((f) => f.endsWith('.test.ts'))
|
|
115
|
+
.sort()) {
|
|
116
|
+
out.push({
|
|
117
|
+
name: f.replace(/\.test\.ts$/, ''),
|
|
118
|
+
file: join(topLevel, f),
|
|
119
|
+
group: '(top-level)',
|
|
120
|
+
});
|
|
101
121
|
}
|
|
102
122
|
}
|
|
103
123
|
return out;
|
|
@@ -200,9 +220,10 @@ switch (command) {
|
|
|
200
220
|
args.includes('--complete') ||
|
|
201
221
|
args.includes('--ci-safe')
|
|
202
222
|
) {
|
|
203
|
-
// --complete
|
|
204
|
-
// the repo's top-level e2e/tests/).
|
|
205
|
-
//
|
|
223
|
+
// --all / --complete / --ci-safe all run the full discovery (every module +
|
|
224
|
+
// the repo's top-level e2e/tests/). Quarantined (`cele2e-ci-unsafe`) tests
|
|
225
|
+
// are skipped by default; only --complete opts back into them (the runner
|
|
226
|
+
// sees the flag — it's kept below). --all-modules stays modules-only.
|
|
206
227
|
const wantTopLevel =
|
|
207
228
|
args.includes('--all') || args.includes('--complete') || args.includes('--ci-safe');
|
|
208
229
|
const suiteRoot = resolveSuiteRoot();
|
|
@@ -222,9 +243,9 @@ switch (command) {
|
|
|
222
243
|
}
|
|
223
244
|
runScript(
|
|
224
245
|
'e2e-run',
|
|
225
|
-
// Strip the discovery aliases; KEEP --
|
|
226
|
-
//
|
|
227
|
-
args.filter((a) => a !== '--all' && a !== '--all-modules'
|
|
246
|
+
// Strip the discovery-only aliases; KEEP --complete (the runner uses it to
|
|
247
|
+
// opt back into quarantined tests) and --ci-safe (harmless/no-op now).
|
|
248
|
+
args.filter((a) => a !== '--all' && a !== '--all-modules'),
|
|
228
249
|
env,
|
|
229
250
|
);
|
|
230
251
|
}
|
|
@@ -236,10 +257,14 @@ switch (command) {
|
|
|
236
257
|
|
|
237
258
|
if (modulePath) {
|
|
238
259
|
const absModulePath = resolve(modulePath);
|
|
239
|
-
runScript(
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
260
|
+
runScript(
|
|
261
|
+
'e2e-run',
|
|
262
|
+
args.filter((a) => a !== modulePath),
|
|
263
|
+
{
|
|
264
|
+
E2E_TEST_DIR: stateDir,
|
|
265
|
+
E2E_MODULE_DIR: absModulePath,
|
|
266
|
+
},
|
|
267
|
+
);
|
|
243
268
|
}
|
|
244
269
|
|
|
245
270
|
// If cwd is not a module, walk sibling modules so the user can invoke
|
|
@@ -261,7 +286,9 @@ switch (command) {
|
|
|
261
286
|
for (const m of matches) {
|
|
262
287
|
console.error(` ${basename(m.moduleDir)}: ${m.matchedTests.join(', ')}`);
|
|
263
288
|
}
|
|
264
|
-
console.error(
|
|
289
|
+
console.error(
|
|
290
|
+
'\nDisambiguate by running from inside the module directory or passing its path.',
|
|
291
|
+
);
|
|
265
292
|
process.exit(1);
|
|
266
293
|
}
|
|
267
294
|
} else if (positionals.length === 0) {
|
|
@@ -364,7 +391,9 @@ switch (command) {
|
|
|
364
391
|
console.log(` Reuse: cele2e run --reuse`);
|
|
365
392
|
console.log(` Tear down: cele2e down`);
|
|
366
393
|
console.log('');
|
|
367
|
-
} catch {
|
|
394
|
+
} catch {
|
|
395
|
+
/* ignore parse errors */
|
|
396
|
+
}
|
|
368
397
|
}
|
|
369
398
|
runScript('e2e-status', args, projectName ? { CELILO_E2E_PROJECT: projectName } : undefined);
|
|
370
399
|
}
|
|
@@ -380,7 +409,10 @@ switch (command) {
|
|
|
380
409
|
// build-infra mutates the shared Docker images — serialize against runs.
|
|
381
410
|
// Released via the lock's exit handler (runBuild may process.exit on failure).
|
|
382
411
|
try {
|
|
383
|
-
acquireRunLock({
|
|
412
|
+
acquireRunLock({
|
|
413
|
+
test: 'build-infra',
|
|
414
|
+
runId: process.env.CELE2E_RUN_ID ?? crypto.randomUUID(),
|
|
415
|
+
});
|
|
384
416
|
} catch (err) {
|
|
385
417
|
if (err instanceof E2eBusyError) {
|
|
386
418
|
console.error(err.message);
|
package/src/container-manager.ts
CHANGED
|
@@ -922,7 +922,7 @@ export async function startNetwork(config: NetworkConfig): Promise<NetworkHandle
|
|
|
922
922
|
if (config.managementVariant !== 'vanilla') {
|
|
923
923
|
assertCliVersion(projectName, composeDir);
|
|
924
924
|
console.log('[progress:start] initializing celilo | celilo initialized');
|
|
925
|
-
// Honest harness (
|
|
925
|
+
// Honest harness (openspec/specs/progressive-zone-disclosure/spec.md): seed ONLY the
|
|
926
926
|
// `internal` zone — the network the management box is genuinely on —
|
|
927
927
|
// plus DNS. dmz/app/secure are NOT pre-seeded; they come into being
|
|
928
928
|
// when a test deploys the firewall (net.deployFirewall()), exactly as
|