@xemahq/repo-build-tooling 0.26.3 → 0.26.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xemahq/repo-build-tooling",
3
- "version": "0.26.3",
3
+ "version": "0.26.4",
4
4
  "description": "Dev-time build tooling shared by every Xema repository. Ships as plain ESM, depending only on @xemahq/release-policy for registry access, 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)",
@@ -51,9 +51,9 @@
51
51
  "@xemahq/release-policy": "^16.0.0"
52
52
  },
53
53
  "scripts": {
54
- "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/composed-image-coordinate-cli.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/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-in-image-closure.test.mjs && node --test src/composed-image-stage.test.mjs && node --test src/composed-image-stage-tree.test.mjs && node --test src/composed-image-install.test.mjs && node --test src/composed-image-proof.test.mjs && node --test src/composed-image-pnpm-context.test.mjs && node --test src/composed-image-sync-injected.test.mjs && node --test src/composed-image-prisma-check.test.mjs && node --test src/composed-image-prisma-engine.test.mjs && node --test src/composed-migrate.test.mjs && node --test src/promote-image.test.mjs && node --test src/arm64-supplement-needed.test.mjs && node --test src/prepush-sanity.test.mjs && node --test src/service-image.test.mjs && node --test src/install-git-hooks.test.mjs"
54
+ "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/composed-image-coordinate-cli.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/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-in-image-closure.test.mjs && node --test src/composed-image-stage.test.mjs && node --test src/composed-image-stage-tree.test.mjs && node --test src/composed-image-install.test.mjs && node --test src/composed-image-proof.test.mjs && node --test src/composed-image-pnpm-context.test.mjs && node --test src/composed-image-sync-injected.test.mjs && node --test src/composed-image-prisma-check.test.mjs && node --test src/composed-image-prisma-engine.test.mjs && node --test src/composed-migrate.test.mjs && node --test src/promote-image.test.mjs && node --test src/arm64-supplement-needed.test.mjs && node --test src/prepush-sanity.test.mjs && node --test src/service-image.test.mjs && node --test src/install-git-hooks.test.mjs && node --test src/image-cpu-arch.test.mjs"
55
55
  },
56
56
  "xemaRelease": {
57
- "sourceDigest": "sha256:e36e1ebf9b6c0b4f12640d1f24212c43bb61d9a5a532279fb4f12a20dde70519"
57
+ "sourceDigest": "sha256:c6b72c65876d81f6a2548bb6da641b908eccb57c9a6ecaa435e31da9b237d75e"
58
58
  }
59
59
  }
@@ -10,6 +10,7 @@ import { tmpdir } from 'node:os';
10
10
  import { join } from 'node:path';
11
11
 
12
12
  import { assertPinsUnchanged } from './composed-image-in-image.mjs';
13
+ import { nodeArch } from './service-image-platform.mjs';
13
14
 
14
15
  /** The install-flag words that make pnpm copy workspace packages into the store layout. */
15
16
  export const injectFlags = () => ['--config.inject-workspace-packages=true', '--config.dedupe-injected-deps=false'];
@@ -26,7 +27,7 @@ export const lockfileOnlyCommand = (filters) => ['pnpm', 'install', '--lockfile-
26
27
  /** The production install for `filters` on `linux/<arch>` as `[file, ...args]`. */
27
28
  export const productionInstallCommand = (filters, arch) => [
28
29
  'pnpm', 'install', '--prod', '--offline', '--ignore-scripts', ...injectFlags(), ...filters, '--frozen-lockfile',
29
- '--os=linux', `--cpu=${arch}`, '--libc=glibc',
30
+ '--os=linux', `--cpu=${nodeArch(arch)}`, '--libc=glibc',
30
31
  ];
31
32
 
32
33
  const defaultExec = (file, args, { cwd }) => execFileSync(file, args, { cwd, stdio: 'inherit' });
@@ -0,0 +1,67 @@
1
+ // pnpm's `--cpu` takes Node `process.arch` names. The OCI name `amd64` is unknown to it and silently skips every
2
+ // optional x64 binary, so an amd64 image lost its @swc/core binding.
3
+ import assert from 'node:assert/strict';
4
+ import { execFileSync } from 'node:child_process';
5
+ import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
6
+ import { tmpdir } from 'node:os';
7
+ import { join } from 'node:path';
8
+ import test from 'node:test';
9
+ import { injectFlags, productionInstallCommand } from './composed-image-install.mjs';
10
+ import { deployArgs } from './service-image-stage.mjs';
11
+ import { nodeArch } from './service-image-platform.mjs';
12
+
13
+ test('nodeArch maps amd64 to x64 and arm64 to arm64, and refuses an arch with no row', () => {
14
+ assert.equal(nodeArch('amd64'), 'x64');
15
+ assert.equal(nodeArch('arm64'), 'arm64');
16
+ assert.throws(() => nodeArch('riscv64'), /no Node arch for image arch 'riscv64'/);
17
+ });
18
+
19
+ test('both install commands pass the Node arch to --cpu and throw for an unknown arch', () => {
20
+ assert.ok(productionInstallCommand([], 'amd64').includes('--cpu=x64'));
21
+ assert.ok(productionInstallCommand([], 'arm64').includes('--cpu=arm64'));
22
+ assert.ok(deployArgs({ service: 's', arch: 'amd64', destination: '/d' }).includes('--cpu=x64'));
23
+ assert.ok(deployArgs({ service: 's', arch: 'arm64', destination: '/d' }).includes('--cpu=arm64'));
24
+ assert.throws(() => productionInstallCommand([], 'riscv64'), /no Node arch/);
25
+ assert.throws(() => deployArgs({ service: 's', arch: 'riscv64', destination: '/d' }), /no Node arch/);
26
+ });
27
+
28
+ // A fixture shaped like @swc/core: a package whose optional dependencies are one binding per platform.
29
+ function fixturePackage(dir, name, manifest) {
30
+ const pkg = join(dir, name.replace(/\W/g, '_'));
31
+ mkdirSync(pkg, { recursive: true });
32
+ writeFileSync(join(pkg, 'package.json'), JSON.stringify({ name, version: '1.0.0', ...manifest }));
33
+ return pkg;
34
+ }
35
+
36
+ test('an amd64 stage installs @swc/core-linux-x64-gnu and no arm64 binding; arm64 the reverse', (t) => {
37
+ const dir = mkdtempSync(join(tmpdir(), 'xema-cpu-arch-'));
38
+ t.after(() => rmSync(dir, { recursive: true, force: true }));
39
+ const x64 = fixturePackage(dir, '@swc/core-linux-x64-gnu', { os: ['linux'], cpu: ['x64'], libc: ['glibc'] });
40
+ const arm64 = fixturePackage(dir, '@swc/core-linux-arm64-gnu', { os: ['linux'], cpu: ['arm64'], libc: ['glibc'] });
41
+ const core = fixturePackage(dir, '@swc/core', {
42
+ optionalDependencies: { '@swc/core-linux-x64-gnu': `file:${x64}`, '@swc/core-linux-arm64-gnu': `file:${arm64}` },
43
+ });
44
+ const store = join(dir, 'store');
45
+ const env = { ...process.env, npm_config_store_dir: store };
46
+ const run = (cwd, args) => {
47
+ try {
48
+ return execFileSync('pnpm', args, { cwd, env, stdio: 'pipe' });
49
+ } catch (error) {
50
+ throw new Error(`pnpm ${args.join(' ')}: ${error.stdout}${error.stderr}`);
51
+ }
52
+ };
53
+ for (const arch of ['amd64', 'arm64']) {
54
+ const app = join(dir, `app-${arch}`);
55
+ mkdirSync(app);
56
+ writeFileSync(join(app, 'package.json'), JSON.stringify({ name: 'app', version: '1.0.0', dependencies: { '@swc/core': `file:${core}` } }));
57
+ // Resolve and fetch every platform's tarball into the store, as the real stage's committed lockfile and primed store do.
58
+ run(app, ['install', '--ignore-workspace', '--ignore-scripts', ...injectFlags(), '--force', '--config.supported-architectures.cpu=*', '--config.supported-architectures.os=*', '--config.supported-architectures.libc=*']);
59
+ rmSync(join(app, 'node_modules'), { recursive: true, force: true });
60
+ const [file, ...args] = productionInstallCommand(['--ignore-workspace'], arch);
61
+ run(app, args);
62
+ assert.equal(file, 'pnpm');
63
+ const present = (binding) => existsSync(join(app, 'node_modules', '.pnpm', 'node_modules', '@swc', binding));
64
+ assert.equal(present('core-linux-x64-gnu'), arch === 'amd64', `${arch}: x64 binding`);
65
+ assert.equal(present('core-linux-arm64-gnu'), arch === 'arm64', `${arch}: arm64 binding`);
66
+ }
67
+ });
@@ -17,6 +17,14 @@ export const IMAGE_PLATFORMS = Object.freeze({
17
17
  arm64: Object.freeze({ node: 'arm64', elfMachine: 183, prismaBinaryTarget: 'linux-arm64-openssl-3.0.x' }),
18
18
  });
19
19
 
20
+ /** The Node `process.arch` name of an image arch (`amd64` → `x64`): what pnpm's `--cpu` takes. Refuses an arch with no row. */
21
+ export function nodeArch(arch) {
22
+ if (!Object.hasOwn(IMAGE_PLATFORMS, arch)) {
23
+ throw new Error(`no Node arch for image arch '${arch}'; known: ${Object.keys(IMAGE_PLATFORMS).join(', ')}`);
24
+ }
25
+ return IMAGE_PLATFORMS[arch].node;
26
+ }
27
+
20
28
  /** `linux/<arch>` → `<arch>`; refuses any platform this recipe does not build. */
21
29
  export function platformArch(platform) {
22
30
  const match = /^linux\/([a-z0-9]+)$/.exec(String(platform ?? ''));
@@ -31,7 +31,7 @@ import { prismaEnginesDirectories } from './composed-image-prisma-engine.mjs';
31
31
  import { copyTool, toolVersion } from './composed-image-stage.mjs';
32
32
  import { MANIFEST_FILE, MANIFEST_SCHEMA } from './service-image-in-image.mjs';
33
33
  import { LOCKFILE, TOOL_NAME, rootBuildInputNames, sha256File } from './staged-image-proof.mjs';
34
- import { IMAGE_PLATFORMS, platformArch, scanNatives } from './service-image-platform.mjs';
34
+ import { IMAGE_PLATFORMS, nodeArch, platformArch, scanNatives } from './service-image-platform.mjs';
35
35
 
36
36
 
37
37
  export const DEFAULT_STAGE_DIR = '.xema-stage';
@@ -48,7 +48,7 @@ export const deployArgs = ({ service, arch, destination }) => [
48
48
  '--offline',
49
49
  '--ignore-scripts',
50
50
  '--os=linux',
51
- `--cpu=${arch}`,
51
+ `--cpu=${nodeArch(arch)}`,
52
52
  '--libc=glibc',
53
53
  '--config.force-legacy-deploy=false',
54
54
  '--config.inject-workspace-packages=true',