@xemahq/repo-build-tooling 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +2 -2
- package/src/composed-image-members.mjs +25 -11
- package/src/composed-image-prebuilt-dockerfile.mjs +17 -2
- package/src/composed-image-prebuilt-dockerfile.test.mjs +14 -1
- package/src/composed-image-stage.mjs +4 -2
- package/src/composed-image-stage.test.mjs +51 -0
- package/src/composed-image.mjs +11 -9
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ pnpm exec xema-scrub-swagger-paths dist
|
|
|
33
33
|
| `xema-check-no-vendored-deps` | Fails on a `workspace:` range for a package the repository does not own |
|
|
34
34
|
| `xema-check-workspace-ranges` | Fails on a `workspace:` range the local package does not satisfy |
|
|
35
35
|
|
|
36
|
-
`xema-composed-image members --write|--check` accepts `--manifests <glob>` (repeatable, default `**/xema-biome.json`), `--canonical-dockerfile <path>` and `--cross-repo-manifest <path>`. `xema-composed-image dockerfile` requires `--tooling-version`, `--copy`, `--cache-id` and `--build-source`. `dockerfile --prebuilt` renders the Dockerfile and `Dockerfile.dockerignore` of an image assembled from `stage <dir> [--output <rel>]…` output (requires `--tooling-version`, `--cache-id`, `--build-source`; `--copy` and `--stage-dir` optional); `load --each` loads every member in its own process. Run `xema-composed-image` with no arguments for the full usage.
|
|
36
|
+
`xema-composed-image members --write|--check` accepts `--manifests <glob>` (repeatable, default `**/xema-biome.json`), `--canonical-dockerfile <path>` and `--cross-repo-manifest <path>`. `xema-composed-image dockerfile` requires `--tooling-version`, `--copy`, `--cache-id` and `--build-source`. `dockerfile --prebuilt` renders the Dockerfile and `Dockerfile.dockerignore` of an image assembled from `stage <dir> [--output <rel>]… [--also <pkg>]…` output (requires `--tooling-version`, `--cache-id`, `--build-source`; `--copy` and `--stage-dir` optional); `load --each` loads every member in its own process. `stage` and `filters` accept `--also <workspace package name>` (repeatable): the package's closure is built and staged (built outputs only, no Prisma checks) alongside the members; an unknown name is refused. Run `xema-composed-image` with no arguments for the full usage.
|
|
37
37
|
|
|
38
38
|
Every step refuses rather than skips. The package has no dependencies.
|
|
39
39
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xemahq/repo-build-tooling",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Dev-time build tooling shared by every Xema repository. Ships as plain ESM with zero dependencies so the published artifact is the reviewed source.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Neuralchowder Inc. <developer@xema.dev> (https://xema.dev)",
|
|
@@ -42,6 +42,6 @@
|
|
|
42
42
|
"test": "node src/scrub-swagger-plugin-paths.mjs --self-test && node --test src/check-workspace-range-matches-local.test.mjs && node --test src/check-no-vendored-deps.test.mjs && node --test src/readiness.test.mjs && node --test src/composed-image.test.mjs && node --test src/ghcr-manifest.test.mjs && node --test src/declaration-refusal.test.mjs && node --test src/smoke-refusal.test.mjs && node --test src/load-image.test.mjs && node --test src/composed-image-coordinate.test.mjs && node --test src/composed-image-dockerfile.test.mjs && node --test src/registry-fetch.test.mjs && node --test src/service-bootstrap.test.mjs && node --test src/service-bootstrap-protocols.test.mjs && node --test src/composed-image-prebuilt-dockerfile.test.mjs && node --test src/composed-image-stage.test.mjs && node --test src/composed-image-sync-injected.test.mjs && node --test src/composed-image-prisma-check.test.mjs && node --test src/composed-migrate.test.mjs"
|
|
43
43
|
},
|
|
44
44
|
"xemaRelease": {
|
|
45
|
-
"sourceDigest": "sha256:
|
|
45
|
+
"sourceDigest": "sha256:65f155b64c9951c7c47a7d2a2b9cf65e08615471f0d5c49097a18460cbed260d"
|
|
46
46
|
}
|
|
47
47
|
}
|
|
@@ -43,10 +43,15 @@ function packageName(root, directory) {
|
|
|
43
43
|
return manifest.name;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* The pnpm `--filter <package>...` arguments covering every member's closure, then
|
|
48
|
+
* each `also` package's (a workspace package name); a name a member already
|
|
49
|
+
* selected is listed once.
|
|
50
|
+
*/
|
|
47
51
|
export function memberFilters(options = {}) {
|
|
48
52
|
const root = options.root ?? process.cwd();
|
|
49
|
-
|
|
53
|
+
const names = readMembers(options).map(([, directory]) => packageName(root, directory));
|
|
54
|
+
return [...new Set([...names, ...(options.also ?? [])])].map((name) => `--filter ${name}...`);
|
|
50
55
|
}
|
|
51
56
|
|
|
52
57
|
|
|
@@ -66,21 +71,30 @@ export function memberManifests({ root = process.cwd(), imageDir = COMPOSED_IMAG
|
|
|
66
71
|
|
|
67
72
|
const defaultExec = (file, args, { cwd }) => execFileSync(file, args, { cwd, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
|
|
68
73
|
|
|
69
|
-
|
|
70
|
-
* The workspace packages the members' filters select (the members and everything
|
|
71
|
-
* they depend on inside the workspace): repository-relative directories, sorted.
|
|
72
|
-
* `exec(file, args, { cwd })` returns stdout; injectable for tests.
|
|
73
|
-
*/
|
|
74
|
-
export function memberClosure({ root = process.cwd(), imageDir = COMPOSED_IMAGE_DIR, exec = defaultExec } = {}) {
|
|
75
|
-
const words = memberFilters({ root, imageDir }).flatMap((filter) => filter.split(' '));
|
|
76
|
-
const listing = exec('pnpm', ['ls', '-r', '--depth', '-1', '--json', ...words], { cwd: root });
|
|
74
|
+
function parseListing(listing) {
|
|
77
75
|
let packages;
|
|
78
76
|
try {
|
|
79
77
|
packages = JSON.parse(listing);
|
|
80
78
|
} catch (error) {
|
|
81
79
|
throw new Error(`pnpm ls did not print JSON: ${error.message}`);
|
|
82
80
|
}
|
|
83
|
-
if (!Array.isArray(packages)
|
|
81
|
+
if (!Array.isArray(packages)) throw new Error('pnpm ls did not print a JSON array');
|
|
82
|
+
return packages;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The workspace packages the members' filters select (the members and everything
|
|
87
|
+
* they depend on inside the workspace): repository-relative directories, sorted.
|
|
88
|
+
* `exec(file, args, { cwd })` returns stdout; injectable for tests.
|
|
89
|
+
*/
|
|
90
|
+
export function memberClosure({ root = process.cwd(), imageDir = COMPOSED_IMAGE_DIR, exec = defaultExec, also = [] } = {}) {
|
|
91
|
+
for (const name of also) {
|
|
92
|
+
const found = parseListing(exec('pnpm', ['ls', '-r', '--depth', '-1', '--json', '--filter', name], { cwd: root }));
|
|
93
|
+
if (!found.some((pkg) => pkg?.name === name)) throw new Error(`--also ${name}: no workspace package has that name`);
|
|
94
|
+
}
|
|
95
|
+
const words = memberFilters({ root, imageDir, also }).flatMap((filter) => filter.split(' '));
|
|
96
|
+
const packages = parseListing(exec('pnpm', ['ls', '-r', '--depth', '-1', '--json', ...words], { cwd: root }));
|
|
97
|
+
if (packages.length === 0) throw new Error('pnpm ls selected no workspace package');
|
|
84
98
|
const real = realpathSync(root);
|
|
85
99
|
const directories = new Set();
|
|
86
100
|
for (const pkg of packages) {
|
|
@@ -123,7 +123,9 @@ export function renderPrebuiltDockerfile({
|
|
|
123
123
|
# assemble source of the closure + the staged outputs, the injected copies synced to
|
|
124
124
|
# their \`files\`, then the proofs on the target arch: Prisma clients run, the
|
|
125
125
|
# member index is written, every member loads ALONE as \`node\`, production.
|
|
126
|
-
# (final) \`/monorepo\`
|
|
126
|
+
# (final) \`/monorepo\` of assemble, shipped as THREE layers so a commit that changes only
|
|
127
|
+
# members' code re-pushes only the layers that changed (see the final stage).
|
|
128
|
+
# Nothing of the tool ships.
|
|
127
129
|
#
|
|
128
130
|
# The \`--filter <package>...\` words below are LITERAL: the content hash of this image reads
|
|
129
131
|
# them to narrow the lockfile to the members' closures.
|
|
@@ -168,9 +170,22 @@ USER node
|
|
|
168
170
|
RUN ${toolMount} \\
|
|
169
171
|
NODE_ENV=production node ${node} load --each${dirFlag}
|
|
170
172
|
|
|
173
|
+
# The final stage is layered by how often each part changes, so a registry and a node
|
|
174
|
+
# already holding the stable layers fetch only the rest. The three copies overlay to
|
|
175
|
+
# exactly assemble's /monorepo (proven by a tree diff of both images).
|
|
176
|
+
# 1 deps root manifests, the external .pnpm store, and the injected stubs that hold
|
|
177
|
+
# only package.json. Stable while the lockfile and the closure's package.json
|
|
178
|
+
# files are unchanged.
|
|
179
|
+
# 2 assemble members, dist, Prisma clients, member index, migrate script: everything
|
|
180
|
+
# but the .pnpm store, whose packages layer 1 already carries.
|
|
181
|
+
# 3 injected the workspace copies \`node_modules/.pnpm/*@file+*\`: \`sync-injected\` fills
|
|
182
|
+
# them from the members' files, so they change with every commit. \`--parents\`
|
|
183
|
+
# keeps their paths under /.
|
|
171
184
|
FROM base
|
|
172
185
|
WORKDIR /monorepo
|
|
173
|
-
COPY --from=
|
|
186
|
+
COPY --from=deps /monorepo /monorepo
|
|
187
|
+
COPY --from=assemble --exclude=node_modules/.pnpm /monorepo /monorepo
|
|
188
|
+
COPY --from=assemble --parents /monorepo/node_modules/.pnpm/*@file+* /
|
|
174
189
|
ENV NODE_ENV=production
|
|
175
190
|
# Set by the IMAGE, never by a deployment (kernel \`COMPOSED_PROCESS_ENV.MemberIndex\`).
|
|
176
191
|
ENV XEMA_COMPOSED_MEMBER_INDEX=/monorepo/xema-composed-member-index.json
|
|
@@ -39,7 +39,7 @@ test('the tool is only bind-mounted, and the final stage carries nothing of it',
|
|
|
39
39
|
assert.doesNotMatch(dockerfile, /\bnpm install|COPY \.xema-stage\/tool/);
|
|
40
40
|
const final = dockerfile.split('\nFROM base\n')[1];
|
|
41
41
|
assert.doesNotMatch(final, /xema-composed-image|repo-build-tooling|npm install|\/opt\//);
|
|
42
|
-
assert.
|
|
42
|
+
assert.doesNotMatch(final, /COPY --from=assemble \/monorepo \/monorepo/);
|
|
43
43
|
assert.match(dockerfile, /USER node\nRUN --mount=\S+ \\\n {2}NODE_ENV=production node \/opt\/xema-composed-image\/src\/composed-image\.mjs load --each\n/);
|
|
44
44
|
assert.match(dockerfile, /cp \/opt\/xema-composed-image\/src\/composed-migrate\.mjs \/monorepo\/xema-composed-migrate\.mjs/);
|
|
45
45
|
});
|
|
@@ -101,3 +101,16 @@ test('a token-using repository gets single-backslash line continuations (a doubl
|
|
|
101
101
|
assert.match(dockerfile, /--mount=type=secret,id=npm_token,required=true \\\n/);
|
|
102
102
|
for (const line of dockerfile.split('\n')) assert.doesNotMatch(line, /\\\\\s*$/, `line ends with a doubled backslash: ${line}`);
|
|
103
103
|
});
|
|
104
|
+
|
|
105
|
+
test('the final stage is layered: deps, then assemble without the .pnpm store, then the injected copies', () => {
|
|
106
|
+
const { dockerfile } = renderPrebuiltDockerfile(options(workspace()));
|
|
107
|
+
const final = dockerfile.split('\nFROM base\n')[1];
|
|
108
|
+
const copies = final.split('\n').filter((line) => line.startsWith('COPY '));
|
|
109
|
+
assert.deepEqual(copies, [
|
|
110
|
+
'COPY --from=deps /monorepo /monorepo',
|
|
111
|
+
'COPY --from=assemble --exclude=node_modules/.pnpm /monorepo /monorepo',
|
|
112
|
+
'COPY --from=assemble --parents /monorepo/node_modules/.pnpm/*@file+* /',
|
|
113
|
+
]);
|
|
114
|
+
assert.ok(final.indexOf('ENV NODE_ENV=production') > final.lastIndexOf('COPY --from=assemble'));
|
|
115
|
+
assert.match(final, /USER node\nCMD \["node", "\/monorepo\/xema-composed-process"\]\n$/);
|
|
116
|
+
});
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
// directory the `--prebuilt` Dockerfile copies from. Docker compiles nothing; this
|
|
4
4
|
// is the only place outputs enter an image, so it is also where they are refused.
|
|
5
5
|
//
|
|
6
|
-
// <dir>/out/<package dir>/<output>/ every closure package's outputs (default: dist)
|
|
6
|
+
// <dir>/out/<package dir>/<output>/ every closure package's outputs (default: dist);
|
|
7
|
+
// `--also <pkg>` adds that package's closure (no member checks)
|
|
7
8
|
// <dir>/out/<member>/node_modules/.prisma/ every member's generated Prisma clients
|
|
8
9
|
// <dir>/tool/ this package (package.json + src, no tests):
|
|
9
10
|
// bind-mounted into the build, never copied into a layer
|
|
@@ -65,6 +66,7 @@ export async function stageBuildOutputs({
|
|
|
65
66
|
imageDir = COMPOSED_IMAGE_DIR,
|
|
66
67
|
dir,
|
|
67
68
|
outputs = DEFAULT_OUTPUTS,
|
|
69
|
+
also = [],
|
|
68
70
|
exec,
|
|
69
71
|
nodeMajor = process.versions.node.split('.')[0],
|
|
70
72
|
formatSpawn,
|
|
@@ -90,7 +92,7 @@ export async function stageBuildOutputs({
|
|
|
90
92
|
|
|
91
93
|
const members = readMembers({ root, imageDir }).map(([key, directory]) => ({ key, directory }));
|
|
92
94
|
const memberDirectories = new Set(members.map((m) => m.directory));
|
|
93
|
-
const closure = memberClosure({ root, imageDir, exec });
|
|
95
|
+
const closure = memberClosure({ root, imageDir, exec, also });
|
|
94
96
|
const schemaJobs = [];
|
|
95
97
|
for (const { key, directory } of members) {
|
|
96
98
|
if (!closure.includes(directory)) problems.push(`${key}: ${directory} is not in the closure pnpm selected`);
|
|
@@ -100,3 +100,54 @@ test('refuses a client generated from another schema than the member carries; th
|
|
|
100
100
|
await assert.rejects(stage(stale), /alpha-db-api: .*differs from the member's prisma\/schema\.prisma .*stale/);
|
|
101
101
|
assert.equal((await stage(ready())).packages, 3);
|
|
102
102
|
});
|
|
103
|
+
|
|
104
|
+
/** An `exec` that answers per-name probes (`--filter <name>`) and the closure listing (`--filter <name>...`). */
|
|
105
|
+
function alsoPnpm(root, known) {
|
|
106
|
+
return (_file, args) => {
|
|
107
|
+
const filters = args.flatMap((a, i) => (args[i - 1] === '--filter' ? [a] : []));
|
|
108
|
+
const names = filters.filter((f) => !f.endsWith('...'));
|
|
109
|
+
if (names.length > 0) return JSON.stringify(known.filter((p) => p.name === names[0]).map((p) => ({ name: p.name, path: path.join(root, p.dir) })));
|
|
110
|
+
const wanted = new Set(filters.map((f) => f.slice(0, -3)));
|
|
111
|
+
const dirs = new Set(['biomes/alpha/api/alpha-api', 'biomes/alpha/api/alpha-db-api', 'packages/sdks/lib-a']);
|
|
112
|
+
for (const p of known) if (wanted.has(p.name)) for (const d of p.closure) dirs.add(d);
|
|
113
|
+
return JSON.stringify([...dirs].map((dir) => ({ name: dir, path: path.join(root, dir) })));
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const EXTRA = [{ name: '@t/extra', dir: 'packages/sdks/extra', closure: ['packages/sdks/extra', 'packages/sdks/extra-dep'] },
|
|
117
|
+
{ name: '@t/lib-a', dir: 'packages/sdks/lib-a', closure: ['packages/sdks/lib-a'] }];
|
|
118
|
+
|
|
119
|
+
function withExtra() {
|
|
120
|
+
const root = ready();
|
|
121
|
+
write(root, 'packages/sdks/extra/package.json', { name: '@t/extra' });
|
|
122
|
+
write(root, 'packages/sdks/extra/dist/index.js', '// extra\n');
|
|
123
|
+
write(root, 'packages/sdks/extra-dep/dist/index.js', '// dep\n');
|
|
124
|
+
return root;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
test('--also stages the named package closure without member checks, and a member named again is idempotent', async () => {
|
|
128
|
+
const root = withExtra();
|
|
129
|
+
const result = await stage(root, { exec: alsoPnpm(root, EXTRA), also: ['@t/extra', '@t/lib-a'] });
|
|
130
|
+
assert.equal(result.packages, 5);
|
|
131
|
+
const staged = files(path.join(root, '.xema-stage/out'));
|
|
132
|
+
for (const expected of ['packages/sdks/extra/dist/index.js', 'packages/sdks/extra-dep/dist/index.js', 'packages/sdks/lib-a/dist/index.js']) {
|
|
133
|
+
assert.ok(staged.includes(expected), expected);
|
|
134
|
+
}
|
|
135
|
+
assert.ok(!staged.some((f) => f.startsWith('packages/sdks/extra/node_modules')));
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('--also refuses an unknown package and a native addon in the extra closure', async () => {
|
|
139
|
+
const root = withExtra();
|
|
140
|
+
await assert.rejects(stage(root, { exec: alsoPnpm(root, EXTRA), also: ['@t/nope'] }), /--also @t\/nope: no workspace package/);
|
|
141
|
+
assert.equal(fs.existsSync(path.join(root, '.xema-stage')), false);
|
|
142
|
+
write(root, 'packages/sdks/extra-dep/dist/a.node', 'x');
|
|
143
|
+
await assert.rejects(stage(root, { exec: alsoPnpm(root, EXTRA), also: ['@t/extra'] }), /extra-dep\/dist\/a\.node/);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('filters and the argument parser carry --also', async () => {
|
|
147
|
+
const root = ready();
|
|
148
|
+
const { memberFilters } = await import('./composed-image-members.mjs');
|
|
149
|
+
const { parseArguments } = await import('./composed-image.mjs');
|
|
150
|
+
assert.deepEqual(memberFilters({ root, also: ['@t/extra', '@t/lib-a', '@t/extra'] }).slice(-2), ['--filter @t/extra...', '--filter @t/lib-a...']);
|
|
151
|
+
assert.equal(memberFilters({ root, also: ['@t/alpha-api'] }).filter((f) => f === '--filter @t/alpha-api...').length, 1);
|
|
152
|
+
assert.deepEqual(parseArguments(['stage', 'd', '--also', 'a', '--also', 'b']).options.also, ['a', 'b']);
|
|
153
|
+
});
|
package/src/composed-image.mjs
CHANGED
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
// derive the member set and write / verify
|
|
25
25
|
// `<image-dir>/image-build-inputs.json` (its existence at a commit IS
|
|
26
26
|
// the repository's composed-process declaration).
|
|
27
|
-
// filters
|
|
27
|
+
// filters [--also <pkg>]…
|
|
28
|
+
// print `--filter <package>...` for every member (and each --also package), so install and
|
|
28
29
|
// build cover exactly the members' closures.
|
|
29
30
|
// dirs print every member's directory, one per line.
|
|
30
31
|
// pins <committed-lockfile>
|
|
@@ -38,10 +39,11 @@
|
|
|
38
39
|
// load --each
|
|
39
40
|
// the same check, one `node` process PER member, so a member that only
|
|
40
41
|
// loads because another loaded first is caught (the prebuilt image's gate).
|
|
41
|
-
// stage <dir> [--output <rel>]…
|
|
42
|
+
// stage <dir> [--output <rel>]… [--also <pkg>]…
|
|
42
43
|
// gather the runner's build outputs (default `dist`) and the members'
|
|
43
44
|
// generated Prisma clients into a build-step directory, with this tool,
|
|
44
|
-
// for the `--prebuilt` Dockerfile (`composed-image-stage.mjs`).
|
|
45
|
+
// for the `--prebuilt` Dockerfile (`composed-image-stage.mjs`). Each `--also`
|
|
46
|
+
// workspace package adds its closure's built outputs (no Prisma checks).
|
|
45
47
|
// sync-injected | prisma-check
|
|
46
48
|
// IN-IMAGE proofs of the prebuilt build: the injected workspace copies hold
|
|
47
49
|
// their packages' `files`; every Prisma client runs on the target arch.
|
|
@@ -454,18 +456,18 @@ export function loadMembersEach({ root = process.cwd(), imageDir = COMPOSED_IMAG
|
|
|
454
456
|
const USAGE =
|
|
455
457
|
'usage: xema-composed-image <command> [--root <dir>] [--image-dir <dir>]\n' +
|
|
456
458
|
' members --write|--check [--manifests <glob>]… [--canonical-dockerfile <path>] [--cross-repo-manifest <path>]\n' +
|
|
457
|
-
' filters | dirs | pins <committed-lockfile> | index <commit> --build-source <repo> | load [--each]\n' +
|
|
458
|
-
' stage <dir> [--output <rel>]… | sync-injected | prisma-check | migrate <member> [--root <dir>]\n' +
|
|
459
|
+
' filters [--also <pkg>]… | dirs | pins <committed-lockfile> | index <commit> --build-source <repo> | load [--each]\n' +
|
|
460
|
+
' stage <dir> [--output <rel>]… [--also <pkg>]… | sync-injected | prisma-check | migrate <member> [--root <dir>]\n' +
|
|
459
461
|
' coordinate --image-name <name> (env: SHA REGISTRY REGISTRY_ORG REGISTRY_USER REGISTRY_TOKEN [TAG_ARGS_FILE])\n' +
|
|
460
462
|
' load-image <image>\n' +
|
|
461
463
|
' smoke-refusal <image> [--member <biomeId>/<componentKey>]x2\n' +
|
|
462
464
|
' dockerfile --write|--check --tooling-version <x.y.z> --copy <dir>… --cache-id <id> --build-source <repo>\n' +
|
|
463
465
|
' dockerfile --prebuilt --write|--check --tooling-version <x.y.z> --cache-id <id> --build-source <repo> [--copy <dir>]… [--stage-dir <dir>]';
|
|
464
466
|
|
|
465
|
-
const REPEATABLE = new Set(['manifests', 'copy', 'member', 'output']);
|
|
467
|
+
const REPEATABLE = new Set(['manifests', 'copy', 'member', 'output', 'also']);
|
|
466
468
|
const BOOLEAN = new Set(['prebuilt', 'each']);
|
|
467
469
|
const VALUED = new Set(['root', 'image-dir', 'manifests', 'canonical-dockerfile', 'cross-repo-manifest', 'build-source',
|
|
468
|
-
'image-name', 'tooling-version', 'cache-id', 'copy', 'member', 'stage-dir', 'output',
|
|
470
|
+
'image-name', 'tooling-version', 'cache-id', 'copy', 'member', 'stage-dir', 'output', 'also',
|
|
469
471
|
]);
|
|
470
472
|
|
|
471
473
|
export function parseArguments(argv) {
|
|
@@ -518,7 +520,7 @@ export function main(argv, { log = console.log, write = (text) => process.stdout
|
|
|
518
520
|
return;
|
|
519
521
|
}
|
|
520
522
|
case 'filters':
|
|
521
|
-
write(`${memberFilters({ root, imageDir }).join(' ')}\n`);
|
|
523
|
+
write(`${memberFilters({ root, imageDir, also: options.also }).join(' ')}\n`);
|
|
522
524
|
return;
|
|
523
525
|
case 'dirs':
|
|
524
526
|
write(`${readMembers({ root, imageDir }).map(([, directory]) => directory).join('\n')}\n`);
|
|
@@ -541,7 +543,7 @@ export function main(argv, { log = console.log, write = (text) => process.stdout
|
|
|
541
543
|
log(`composed members: all ${loadMembers({ root, imageDir })} module graph(s) load`);
|
|
542
544
|
return;
|
|
543
545
|
case 'stage': {
|
|
544
|
-
return stageBuildOutputs({ root, imageDir, dir: argument, outputs: options.output }).then((staged) => {
|
|
546
|
+
return stageBuildOutputs({ root, imageDir, dir: argument, outputs: options.output, also: options.also }).then((staged) => {
|
|
545
547
|
log(`composed stage ${argument}: ${staged.packages} package(s), ${staged.outputs} output dir(s), ${staged.files} file(s), tool ${staged.toolFiles} file(s)`);
|
|
546
548
|
});
|
|
547
549
|
}
|