@superblocksteam/sdk 2.0.123 → 2.0.124-next.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/.turbo/turbo-build.log +1 -1
- package/dist/cli-replacement/automatic-upgrades.d.ts +37 -1
- package/dist/cli-replacement/automatic-upgrades.d.ts.map +1 -1
- package/dist/cli-replacement/automatic-upgrades.js +162 -10
- package/dist/cli-replacement/automatic-upgrades.js.map +1 -1
- package/dist/cli-replacement/automatic-upgrades.test.js +377 -8
- package/dist/cli-replacement/automatic-upgrades.test.js.map +1 -1
- package/dist/cli-replacement/dependency-install-classifier.d.mts +21 -0
- package/dist/cli-replacement/dependency-install-classifier.d.mts.map +1 -0
- package/dist/cli-replacement/dependency-install-classifier.mjs +83 -0
- package/dist/cli-replacement/dependency-install-classifier.mjs.map +1 -0
- package/dist/cli-replacement/dependency-install-classifier.test.d.mts +2 -0
- package/dist/cli-replacement/dependency-install-classifier.test.d.mts.map +1 -0
- package/dist/cli-replacement/dependency-install-classifier.test.mjs +51 -0
- package/dist/cli-replacement/dependency-install-classifier.test.mjs.map +1 -0
- package/dist/cli-replacement/dev-s3-restore.test.mjs +170 -14
- package/dist/cli-replacement/dev-s3-restore.test.mjs.map +1 -1
- package/dist/cli-replacement/dev-startup-git-before-dbfs-order.test.mjs +33 -2
- package/dist/cli-replacement/dev-startup-git-before-dbfs-order.test.mjs.map +1 -1
- package/dist/cli-replacement/dev-token-priming.test.d.mts +31 -0
- package/dist/cli-replacement/dev-token-priming.test.d.mts.map +1 -0
- package/dist/cli-replacement/dev-token-priming.test.mjs +87 -0
- package/dist/cli-replacement/dev-token-priming.test.mjs.map +1 -0
- package/dist/cli-replacement/dev.d.mts +36 -0
- package/dist/cli-replacement/dev.d.mts.map +1 -1
- package/dist/cli-replacement/dev.interception.test.d.mts +2 -0
- package/dist/cli-replacement/dev.interception.test.d.mts.map +1 -0
- package/dist/cli-replacement/dev.interception.test.mjs +68 -0
- package/dist/cli-replacement/dev.interception.test.mjs.map +1 -0
- package/dist/cli-replacement/dev.mjs +396 -62
- package/dist/cli-replacement/dev.mjs.map +1 -1
- package/dist/cli-replacement/home-npmrc.d.mts +180 -0
- package/dist/cli-replacement/home-npmrc.d.mts.map +1 -0
- package/dist/cli-replacement/home-npmrc.mjs +283 -0
- package/dist/cli-replacement/home-npmrc.mjs.map +1 -0
- package/dist/cli-replacement/home-npmrc.test.d.mts +10 -0
- package/dist/cli-replacement/home-npmrc.test.d.mts.map +1 -0
- package/dist/cli-replacement/home-npmrc.test.mjs +582 -0
- package/dist/cli-replacement/home-npmrc.test.mjs.map +1 -0
- package/dist/cli-replacement/install-packages.classify.test.d.mts +2 -0
- package/dist/cli-replacement/install-packages.classify.test.d.mts.map +1 -0
- package/dist/cli-replacement/install-packages.classify.test.mjs +125 -0
- package/dist/cli-replacement/install-packages.classify.test.mjs.map +1 -0
- package/dist/cli-replacement/install-packages.npm-registry.test.d.mts +2 -0
- package/dist/cli-replacement/install-packages.npm-registry.test.d.mts.map +1 -0
- package/dist/cli-replacement/install-packages.npm-registry.test.mjs +260 -0
- package/dist/cli-replacement/install-packages.npm-registry.test.mjs.map +1 -0
- package/dist/cli-replacement/post-upgrade-lockfile-strip.d.mts +58 -0
- package/dist/cli-replacement/post-upgrade-lockfile-strip.d.mts.map +1 -0
- package/dist/cli-replacement/post-upgrade-lockfile-strip.mjs +224 -0
- package/dist/cli-replacement/post-upgrade-lockfile-strip.mjs.map +1 -0
- package/dist/cli-replacement/post-upgrade-lockfile-strip.test.d.mts +11 -0
- package/dist/cli-replacement/post-upgrade-lockfile-strip.test.d.mts.map +1 -0
- package/dist/cli-replacement/post-upgrade-lockfile-strip.test.mjs +317 -0
- package/dist/cli-replacement/post-upgrade-lockfile-strip.test.mjs.map +1 -0
- package/dist/cli-replacement/userconfig-env.integration.test.d.mts +26 -0
- package/dist/cli-replacement/userconfig-env.integration.test.d.mts.map +1 -0
- package/dist/cli-replacement/userconfig-env.integration.test.mjs +148 -0
- package/dist/cli-replacement/userconfig-env.integration.test.mjs.map +1 -0
- package/dist/dev-utils/dev-server-metrics.d.mts +25 -0
- package/dist/dev-utils/dev-server-metrics.d.mts.map +1 -1
- package/dist/dev-utils/dev-server-metrics.mjs +84 -0
- package/dist/dev-utils/dev-server-metrics.mjs.map +1 -1
- package/dist/dev-utils/dev-server-metrics.test.d.mts +2 -0
- package/dist/dev-utils/dev-server-metrics.test.d.mts.map +1 -0
- package/dist/dev-utils/dev-server-metrics.test.mjs +26 -0
- package/dist/dev-utils/dev-server-metrics.test.mjs.map +1 -0
- package/dist/dev-utils/dev-server.d.mts +23 -1
- package/dist/dev-utils/dev-server.d.mts.map +1 -1
- package/dist/dev-utils/dev-server.mjs +21 -9
- package/dist/dev-utils/dev-server.mjs.map +1 -1
- package/dist/dev-utils/dev-server.status.test.d.mts +2 -0
- package/dist/dev-utils/dev-server.status.test.d.mts.map +1 -0
- package/dist/dev-utils/dev-server.status.test.mjs +41 -0
- package/dist/dev-utils/dev-server.status.test.mjs.map +1 -0
- package/dist/dev-utils/token-manager.d.ts +31 -0
- package/dist/dev-utils/token-manager.d.ts.map +1 -1
- package/dist/dev-utils/token-manager.js +34 -0
- package/dist/dev-utils/token-manager.js.map +1 -1
- package/dist/telemetry/local-obs.js +1 -1
- package/dist/telemetry/local-obs.js.map +1 -1
- package/dist/telemetry/util.js +1 -1
- package/dist/types/scoped-jwt-token-payload.d.ts +1 -0
- package/dist/types/scoped-jwt-token-payload.d.ts.map +1 -1
- package/dist/version-control.d.mts.map +1 -1
- package/dist/version-control.mjs +6 -7
- package/dist/version-control.mjs.map +1 -1
- package/package.json +12 -12
- package/src/cli-replacement/automatic-upgrades.test.ts +530 -8
- package/src/cli-replacement/automatic-upgrades.ts +179 -7
- package/src/cli-replacement/dependency-install-classifier.mts +118 -0
- package/src/cli-replacement/dependency-install-classifier.test.mts +72 -0
- package/src/cli-replacement/dev-s3-restore.test.mts +210 -14
- package/src/cli-replacement/dev-startup-git-before-dbfs-order.test.mts +35 -2
- package/src/cli-replacement/dev-token-priming.test.mts +103 -0
- package/src/cli-replacement/dev.interception.test.mts +80 -0
- package/src/cli-replacement/dev.mts +495 -92
- package/src/cli-replacement/home-npmrc.mts +409 -0
- package/src/cli-replacement/home-npmrc.test.mts +757 -0
- package/src/cli-replacement/install-packages.classify.test.mts +168 -0
- package/src/cli-replacement/install-packages.npm-registry.test.mts +345 -0
- package/src/cli-replacement/post-upgrade-lockfile-strip.mts +296 -0
- package/src/cli-replacement/post-upgrade-lockfile-strip.test.mts +482 -0
- package/src/cli-replacement/userconfig-env.integration.test.mts +189 -0
- package/src/dev-utils/dev-server-metrics.mts +96 -0
- package/src/dev-utils/dev-server-metrics.test.mts +38 -0
- package/src/dev-utils/dev-server.mts +48 -8
- package/src/dev-utils/dev-server.status.test.mts +58 -0
- package/src/dev-utils/token-manager.ts +36 -0
- package/src/telemetry/local-obs.ts +1 -1
- package/src/telemetry/util.ts +1 -1
- package/src/types/scoped-jwt-token-payload.ts +1 -0
- package/src/version-control.mts +8 -6
- package/tsconfig.tsbuildinfo +1 -1
- package/.turbo/turbo-publish-package.log +0 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Failure-classification coverage for the dev-server startup `installPackages`
|
|
3
|
+
* (APPS-4450). When the verification install fails, `installPackages` no longer
|
|
4
|
+
* rethrows the raw `exec` rejection — it classifies the npm `--json` envelope
|
|
5
|
+
* into a structured `DependencyInstallError` and throws the
|
|
6
|
+
* `InitialInstallFailed` marker so the `dev()` catch can degrade by origin.
|
|
7
|
+
*
|
|
8
|
+
* Mirrors the mock style of the sibling `install-packages.npm-registry.test.mts`
|
|
9
|
+
* (stub `node:child_process`, `package-manager-detector`, `node:fs/promises`,
|
|
10
|
+
* and the logging module; hand a fake `NpmRegistryClient`). The one new wrinkle
|
|
11
|
+
* is the failure path: the promisified `exec` rejects with an error object that
|
|
12
|
+
* carries the ERESOLVE `--json` payload on `.stdout` and the npm error-code
|
|
13
|
+
* banner on `.stderr`, exactly as `util.promisify(child_process.exec)` does in
|
|
14
|
+
* production (it preserves stdout/stderr on the rejected error).
|
|
15
|
+
*/
|
|
16
|
+
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
17
|
+
|
|
18
|
+
import type { installPackages as InstallPackagesType } from "./dev.mjs";
|
|
19
|
+
|
|
20
|
+
const execMock = vi.fn();
|
|
21
|
+
const execFileMock = vi.fn();
|
|
22
|
+
vi.mock("node:child_process", () => ({
|
|
23
|
+
default: { exec: execMock, execFile: execFileMock },
|
|
24
|
+
exec: execMock,
|
|
25
|
+
execFile: execFileMock,
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
const detectMock = vi.fn();
|
|
29
|
+
vi.mock("package-manager-detector/detect", () => ({
|
|
30
|
+
detect: (...args: unknown[]) => detectMock(...args),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
const resolveCommandMock = vi.fn();
|
|
34
|
+
vi.mock("package-manager-detector", () => ({
|
|
35
|
+
resolveCommand: (...args: unknown[]) => resolveCommandMock(...args),
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
vi.mock("node:fs/promises", () => ({
|
|
39
|
+
readFile: vi.fn(async () => "{}"),
|
|
40
|
+
writeFile: vi.fn(async () => undefined),
|
|
41
|
+
// `installPackages` ensures `<app>/.superblocks/logs` exists (npm's
|
|
42
|
+
// `logs-dir` target) before spawning the install; without this stub the
|
|
43
|
+
// real `mkdir` is undefined and throws before the exec mock, misclassifying
|
|
44
|
+
// the failure as `unknown`. Mirrors the sibling npm-registry test.
|
|
45
|
+
mkdir: vi.fn(async () => undefined),
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
const mockLogger = {
|
|
49
|
+
info: vi.fn(),
|
|
50
|
+
warn: vi.fn(),
|
|
51
|
+
error: vi.fn(),
|
|
52
|
+
debug: vi.fn(),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
vi.mock("../telemetry/logging.js", () => ({
|
|
56
|
+
getLogger: () => mockLogger,
|
|
57
|
+
getErrorMeta: (err: unknown) => ({
|
|
58
|
+
error: { kind: "Error", message: err instanceof Error ? err.message : "" },
|
|
59
|
+
}),
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
type NpmRegistryFetchResult = {
|
|
63
|
+
source: "configured" | "not-configured" | "stale" | "unreachable";
|
|
64
|
+
config: { configured: boolean; allowInstallScripts?: boolean };
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
function fakeRegistryClient(
|
|
68
|
+
result: NpmRegistryFetchResult | (() => Promise<NpmRegistryFetchResult>),
|
|
69
|
+
) {
|
|
70
|
+
return {
|
|
71
|
+
getConfig: vi.fn(async () =>
|
|
72
|
+
typeof result === "function" ? result() : result,
|
|
73
|
+
),
|
|
74
|
+
} as unknown as Parameters<typeof InstallPackagesType>[2];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function notConfigured(): NpmRegistryFetchResult {
|
|
78
|
+
return { source: "not-configured", config: { configured: false } };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The ERESOLVE `--json` envelope npm writes to stdout when a peer-dependency
|
|
83
|
+
* conflict aborts the install. `util.promisify(exec)` rejects with an Error
|
|
84
|
+
* whose `.stdout` carries this body and whose `.stderr` carries the
|
|
85
|
+
* `npm error code ERESOLVE` banner — both preserved separately on the
|
|
86
|
+
* rejected error (the contract the classifier relies on).
|
|
87
|
+
*/
|
|
88
|
+
const ERESOLVE_JSON_STDOUT = JSON.stringify({
|
|
89
|
+
error: {
|
|
90
|
+
code: "ERESOLVE",
|
|
91
|
+
summary: "unable to resolve dependency tree",
|
|
92
|
+
detail:
|
|
93
|
+
"Found: react@17.0.2\nCould not resolve dependency:\npeer react@^18.0.0 from @some/pkg@1.0.0",
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const ERESOLVE_STDERR = [
|
|
98
|
+
"npm error code ERESOLVE",
|
|
99
|
+
"npm error ERESOLVE unable to resolve dependency tree",
|
|
100
|
+
].join("\n");
|
|
101
|
+
|
|
102
|
+
beforeAll(async () => {
|
|
103
|
+
await import("./dev.mjs");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
beforeEach(() => {
|
|
107
|
+
vi.clearAllMocks();
|
|
108
|
+
detectMock.mockResolvedValue({
|
|
109
|
+
name: "npm",
|
|
110
|
+
agent: "npm",
|
|
111
|
+
version: "10.0.0",
|
|
112
|
+
});
|
|
113
|
+
resolveCommandMock.mockImplementation(
|
|
114
|
+
(_agent: string, _action: string, args: string[]) => ({
|
|
115
|
+
command: "npm",
|
|
116
|
+
args: ["install", ...args],
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe("installPackages classifies install failures (APPS-4450)", () => {
|
|
122
|
+
it("throws InitialInstallFailed(dependency_conflict) on an ERESOLVE install", async () => {
|
|
123
|
+
const { installPackages } = await import("./dev.mjs");
|
|
124
|
+
const client = fakeRegistryClient(notConfigured());
|
|
125
|
+
|
|
126
|
+
// Reject exactly the way `util.promisify(child_process.exec)` does: the
|
|
127
|
+
// rejected value is an Error with `.stdout` and `.stderr` preserved
|
|
128
|
+
// separately. The ERESOLVE `--json` body lives on `.stdout`.
|
|
129
|
+
execMock.mockImplementation(
|
|
130
|
+
(
|
|
131
|
+
_cmd: string,
|
|
132
|
+
_opts: unknown,
|
|
133
|
+
cb?: (err: unknown, result?: { stdout: string }) => void,
|
|
134
|
+
) => {
|
|
135
|
+
const callback = (typeof _opts === "function" ? _opts : cb) as (
|
|
136
|
+
err: unknown,
|
|
137
|
+
result?: { stdout: string },
|
|
138
|
+
) => void;
|
|
139
|
+
const err = Object.assign(
|
|
140
|
+
new Error("Command failed: npm install --fund=false --audit=false"),
|
|
141
|
+
{
|
|
142
|
+
stdout: ERESOLVE_JSON_STDOUT,
|
|
143
|
+
stderr: ERESOLVE_STDERR,
|
|
144
|
+
code: 1,
|
|
145
|
+
},
|
|
146
|
+
);
|
|
147
|
+
callback(err);
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
await expect(
|
|
152
|
+
installPackages("/tmp/app", mockLogger as never, client),
|
|
153
|
+
).rejects.toMatchObject({
|
|
154
|
+
name: "InitialInstallFailed",
|
|
155
|
+
serverError: {
|
|
156
|
+
category: "dependency_conflict",
|
|
157
|
+
npmErrorCode: "ERESOLVE",
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Guard against the mock being silently bypassed: if `dev.mjs` ever binds
|
|
162
|
+
// `exec` to the REAL `child_process.exec` (a module-scope capture before
|
|
163
|
+
// this file's mock is live), real npm runs against the missing `/tmp/app`
|
|
164
|
+
// and the classification quietly degrades to unknown/undefined. Asserting
|
|
165
|
+
// the stub fired turns that into an explicit failure instead. (APPS-4450)
|
|
166
|
+
expect(execMock).toHaveBeenCalledTimes(1);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Argv-matrix coverage for the dev-server startup `installPackages` honoring
|
|
3
|
+
* the per-org `allow_install_scripts` policy (APPS-4251).
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the structure of
|
|
6
|
+
* `packages/vite-plugin-file-sync/src/ai-service/app-interface/shell.npm-registry.test.ts`
|
|
7
|
+
* (the AppShell-side coverage): we stub `package-manager-detector`, capture
|
|
8
|
+
* the args handed to `resolveCommand`, drive a fake `NpmRegistryClient`, and
|
|
9
|
+
* assert whether `--ignore-scripts` made it onto the install argv.
|
|
10
|
+
*
|
|
11
|
+
* Why exercise this here rather than via the full `dev()` test surface:
|
|
12
|
+
* `installPackages` is the unit that picked up the new policy plumbing.
|
|
13
|
+
* Testing through `dev()` would need ~250 lines of mock setup unrelated to
|
|
14
|
+
* the policy decision, and the test would still be asserting the same
|
|
15
|
+
* `resolveCommand`-args invariant. The function is intentionally exported
|
|
16
|
+
* from `dev.mts` for this purpose.
|
|
17
|
+
*/
|
|
18
|
+
import {
|
|
19
|
+
beforeAll,
|
|
20
|
+
beforeEach,
|
|
21
|
+
describe,
|
|
22
|
+
expect,
|
|
23
|
+
it,
|
|
24
|
+
vi,
|
|
25
|
+
type Mock,
|
|
26
|
+
} from "vitest";
|
|
27
|
+
|
|
28
|
+
import type { installPackages as InstallPackagesType } from "./dev.mjs";
|
|
29
|
+
|
|
30
|
+
const execMock = vi.fn();
|
|
31
|
+
const execFileMock = vi.fn();
|
|
32
|
+
vi.mock("node:child_process", () => ({
|
|
33
|
+
default: { exec: execMock, execFile: execFileMock },
|
|
34
|
+
exec: execMock,
|
|
35
|
+
execFile: execFileMock,
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
const detectMock = vi.fn();
|
|
39
|
+
vi.mock("package-manager-detector/detect", () => ({
|
|
40
|
+
detect: (...args: unknown[]) => detectMock(...args),
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
const resolveCommandMock = vi.fn();
|
|
44
|
+
vi.mock("package-manager-detector", () => ({
|
|
45
|
+
resolveCommand: (...args: unknown[]) => resolveCommandMock(...args),
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
vi.mock("node:fs/promises", () => ({
|
|
49
|
+
readFile: vi.fn(async () => "{}"),
|
|
50
|
+
writeFile: vi.fn(async () => undefined),
|
|
51
|
+
// `installPackages` ensures the `<app>/.superblocks/logs` dir exists
|
|
52
|
+
// before spawning the install (npm's `logs-dir` target).
|
|
53
|
+
mkdir: vi.fn(async () => undefined),
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
const mockLogger = {
|
|
57
|
+
info: vi.fn(),
|
|
58
|
+
warn: vi.fn(),
|
|
59
|
+
error: vi.fn(),
|
|
60
|
+
debug: vi.fn(),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
vi.mock("../telemetry/logging.js", () => ({
|
|
64
|
+
getLogger: () => mockLogger,
|
|
65
|
+
getErrorMeta: (err: unknown) => ({
|
|
66
|
+
error: { kind: "Error", message: err instanceof Error ? err.message : "" },
|
|
67
|
+
}),
|
|
68
|
+
}));
|
|
69
|
+
|
|
70
|
+
type NpmRegistryFetchResult = {
|
|
71
|
+
source: "configured" | "not-configured" | "stale";
|
|
72
|
+
config: {
|
|
73
|
+
configured: boolean;
|
|
74
|
+
allowInstallScripts?: boolean;
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
function fakeRegistryClient(
|
|
79
|
+
result: NpmRegistryFetchResult | (() => Promise<NpmRegistryFetchResult>),
|
|
80
|
+
) {
|
|
81
|
+
return {
|
|
82
|
+
getConfig: vi.fn(async () =>
|
|
83
|
+
typeof result === "function" ? result() : result,
|
|
84
|
+
),
|
|
85
|
+
// The dev-server install path only calls `getConfig()`; the rest of the
|
|
86
|
+
// `NpmRegistryClient` surface (JWT refresh, cache TTL) is exercised by
|
|
87
|
+
// the vite-plugin-file-sync side. Casting through `unknown` lets us
|
|
88
|
+
// hand this minimal stub to a parameter typed `NpmRegistryClient`
|
|
89
|
+
// without dragging the full class into the test.
|
|
90
|
+
} as unknown as Parameters<typeof InstallPackagesType>[2];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function notConfigured(allowInstallScripts?: boolean): NpmRegistryFetchResult {
|
|
94
|
+
return {
|
|
95
|
+
source: "not-configured",
|
|
96
|
+
config: {
|
|
97
|
+
configured: false,
|
|
98
|
+
...(allowInstallScripts !== undefined ? { allowInstallScripts } : {}),
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Pre-import `./dev.mjs` once so the heavy module-load cost (it transitively
|
|
104
|
+
// pulls in `vite-plugin-file-sync/git-service`, `AiService`, etc.) is paid
|
|
105
|
+
// outside any test's per-test timeout. Under turbo parallelism (CI runs the
|
|
106
|
+
// `cli` unit-test job alongside ai-service-templates, cli, mcp-server, and
|
|
107
|
+
// sdk in the same worker pool), the first dynamic `await import("./dev.mjs")`
|
|
108
|
+
// inside a test was being CPU-starved past the default 5s testTimeout — the
|
|
109
|
+
// test that owned the cold import then timed out, leaked its in-flight
|
|
110
|
+
// `resolveCommand` call past the `beforeEach` `clearAllMocks`, and the *next*
|
|
111
|
+
// test observed two recorded calls instead of one. Hoisting to `beforeAll`
|
|
112
|
+
// keeps the per-test surface fast and order-independent. The repeated
|
|
113
|
+
// `await import("./dev.mjs")` lines below remain as cheap cached lookups —
|
|
114
|
+
// no behavioural change.
|
|
115
|
+
beforeAll(async () => {
|
|
116
|
+
await import("./dev.mjs");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
beforeEach(() => {
|
|
120
|
+
vi.clearAllMocks();
|
|
121
|
+
// Default: npm package manager, install resolves to a real command so we
|
|
122
|
+
// can read back the args the call site passed in.
|
|
123
|
+
detectMock.mockResolvedValue({
|
|
124
|
+
name: "npm",
|
|
125
|
+
agent: "npm",
|
|
126
|
+
version: "10.0.0",
|
|
127
|
+
});
|
|
128
|
+
resolveCommandMock.mockImplementation(
|
|
129
|
+
(_agent: string, _action: string, args: string[]) => ({
|
|
130
|
+
command: "npm",
|
|
131
|
+
args: ["install", ...args],
|
|
132
|
+
}),
|
|
133
|
+
);
|
|
134
|
+
execMock.mockImplementation(
|
|
135
|
+
(
|
|
136
|
+
_cmd: string,
|
|
137
|
+
_opts: unknown,
|
|
138
|
+
cb?: (err: null, result: { stdout: string }) => void,
|
|
139
|
+
) => {
|
|
140
|
+
if (typeof _opts === "function") {
|
|
141
|
+
(_opts as (err: null, result: { stdout: string }) => void)(null, {
|
|
142
|
+
stdout: "ok",
|
|
143
|
+
});
|
|
144
|
+
} else if (cb) {
|
|
145
|
+
cb(null, { stdout: "ok" });
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
function getResolveCommandArgs(): string[] {
|
|
152
|
+
expect(resolveCommandMock).toHaveBeenCalledTimes(1);
|
|
153
|
+
const [, , args] = (resolveCommandMock as Mock).mock.calls[0] as [
|
|
154
|
+
string,
|
|
155
|
+
string,
|
|
156
|
+
string[],
|
|
157
|
+
];
|
|
158
|
+
return args;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Pull the exec options (incl. `env`) out of the spawned subprocess
|
|
162
|
+
* call. We assert against the env-overlay because that is the mechanism
|
|
163
|
+
* userconfig pinning uses for npm AND pnpm (pnpm rejects `--userconfig=`
|
|
164
|
+
* as a CLI flag — pnpm/pnpm#6036). */
|
|
165
|
+
function getExecOptions(): { cwd?: string; env?: NodeJS.ProcessEnv } {
|
|
166
|
+
const lastCall = (execMock as Mock).mock.calls.at(-1) as [
|
|
167
|
+
string,
|
|
168
|
+
{ cwd?: string; env?: NodeJS.ProcessEnv },
|
|
169
|
+
...unknown[],
|
|
170
|
+
];
|
|
171
|
+
return lastCall[1];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
describe("installPackages honors allow_install_scripts policy", () => {
|
|
175
|
+
it("appends --ignore-scripts when allow_install_scripts === false", async () => {
|
|
176
|
+
const { installPackages } = await import("./dev.mjs");
|
|
177
|
+
const client = fakeRegistryClient(notConfigured(false));
|
|
178
|
+
|
|
179
|
+
await installPackages("/tmp/app", mockLogger as never, client);
|
|
180
|
+
|
|
181
|
+
const args = getResolveCommandArgs();
|
|
182
|
+
expect(args).toContain("--ignore-scripts");
|
|
183
|
+
// Existing baseline npm flags must survive.
|
|
184
|
+
expect(args).toContain("--fund=false");
|
|
185
|
+
expect(args).toContain("--audit=false");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("does NOT append --ignore-scripts when allow_install_scripts === true", async () => {
|
|
189
|
+
const { installPackages } = await import("./dev.mjs");
|
|
190
|
+
const client = fakeRegistryClient(notConfigured(true));
|
|
191
|
+
|
|
192
|
+
await installPackages("/tmp/app", mockLogger as never, client);
|
|
193
|
+
|
|
194
|
+
expect(getResolveCommandArgs()).not.toContain("--ignore-scripts");
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("does NOT append --ignore-scripts when allow_install_scripts is unset", async () => {
|
|
198
|
+
const { installPackages } = await import("./dev.mjs");
|
|
199
|
+
const client = fakeRegistryClient(notConfigured(undefined));
|
|
200
|
+
|
|
201
|
+
await installPackages("/tmp/app", mockLogger as never, client);
|
|
202
|
+
|
|
203
|
+
expect(getResolveCommandArgs()).not.toContain("--ignore-scripts");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("does NOT append --ignore-scripts when no client is wired in (test/opt-out path)", async () => {
|
|
207
|
+
const { installPackages } = await import("./dev.mjs");
|
|
208
|
+
|
|
209
|
+
await installPackages("/tmp/app", mockLogger as never);
|
|
210
|
+
|
|
211
|
+
expect(getResolveCommandArgs()).not.toContain("--ignore-scripts");
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("swallows policy-resolution errors and proceeds without --ignore-scripts", async () => {
|
|
215
|
+
// A transient registry-endpoint outage with no cache must not abort the
|
|
216
|
+
// user's startup install. The client surfaces the throw; we log and
|
|
217
|
+
// fall back to "scripts allowed" (today's behaviour). The 404 /
|
|
218
|
+
// last-known-good preservation that prevents this in practice is
|
|
219
|
+
// covered by `npm-registry.test.ts` on the vite-plugin-file-sync side.
|
|
220
|
+
const { installPackages } = await import("./dev.mjs");
|
|
221
|
+
const client = fakeRegistryClient(async () => {
|
|
222
|
+
throw new Error("registry endpoint unreachable");
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
await installPackages("/tmp/app", mockLogger as never, client);
|
|
226
|
+
|
|
227
|
+
expect(getResolveCommandArgs()).not.toContain("--ignore-scripts");
|
|
228
|
+
expect(mockLogger.warn).toHaveBeenCalledWith(
|
|
229
|
+
expect.stringContaining("Could not resolve npm install-scripts policy"),
|
|
230
|
+
expect.objectContaining({ error: expect.any(Object) }),
|
|
231
|
+
);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("appends --ignore-scripts under pnpm too (no npm-only baseline flags)", async () => {
|
|
235
|
+
detectMock.mockResolvedValue({
|
|
236
|
+
name: "pnpm",
|
|
237
|
+
agent: "pnpm",
|
|
238
|
+
version: "9.0.0",
|
|
239
|
+
});
|
|
240
|
+
resolveCommandMock.mockImplementation(
|
|
241
|
+
(_agent: string, _action: string, args: string[]) => ({
|
|
242
|
+
command: "pnpm",
|
|
243
|
+
args: ["install", ...args],
|
|
244
|
+
}),
|
|
245
|
+
);
|
|
246
|
+
const { installPackages } = await import("./dev.mjs");
|
|
247
|
+
const client = fakeRegistryClient(notConfigured(false));
|
|
248
|
+
|
|
249
|
+
await installPackages("/tmp/app", mockLogger as never, client);
|
|
250
|
+
|
|
251
|
+
const args = getResolveCommandArgs();
|
|
252
|
+
expect(args).toContain("--ignore-scripts");
|
|
253
|
+
expect(args).not.toContain("--fund=false");
|
|
254
|
+
expect(args).not.toContain("--audit=false");
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// ---------------------------------------------------------------------------
|
|
259
|
+
// Userconfig plumbing for the dev-server startup install.
|
|
260
|
+
//
|
|
261
|
+
// `installPackages` points npm/pnpm at the Superblocks-owned userconfig
|
|
262
|
+
// (`~/.superblocks/npmrc`) so the user-app install resolves
|
|
263
|
+
// `@superblocksteam/library`, `@superblocksteam/sdk-api`, and any private
|
|
264
|
+
// scope through the same registry config the CLI auto-upgrade uses.
|
|
265
|
+
//
|
|
266
|
+
// The mechanism is the spawned subprocess env overlay (both
|
|
267
|
+
// `NPM_CONFIG_USERCONFIG` and `PNPM_CONFIG_USERCONFIG`), not a CLI flag.
|
|
268
|
+
// pnpm rejects `--userconfig=` (pnpm/pnpm#6036), and pnpm 11+ stopped
|
|
269
|
+
// honouring the `npm_config_*` env vars — setting both forms covers
|
|
270
|
+
// npm, pnpm <= 10, and pnpm 11+ uniformly.
|
|
271
|
+
// `userconfig-env.integration.test.mts` in this directory exercises the
|
|
272
|
+
// real npm + pnpm binaries to pin that contract.
|
|
273
|
+
// ---------------------------------------------------------------------------
|
|
274
|
+
|
|
275
|
+
const USERCONFIG_PATTERN = /[/\\]\.superblocks[/\\]npmrc$/;
|
|
276
|
+
|
|
277
|
+
describe("installPackages userconfig env plumbing", () => {
|
|
278
|
+
it("sets both NPM_CONFIG_USERCONFIG and PNPM_CONFIG_USERCONFIG under npm", async () => {
|
|
279
|
+
const { installPackages } = await import("./dev.mjs");
|
|
280
|
+
const client = fakeRegistryClient(notConfigured(undefined));
|
|
281
|
+
|
|
282
|
+
await installPackages("/tmp/app", mockLogger as never, client);
|
|
283
|
+
|
|
284
|
+
const opts = getExecOptions();
|
|
285
|
+
expect(opts.env?.NPM_CONFIG_USERCONFIG).toMatch(USERCONFIG_PATTERN);
|
|
286
|
+
expect(opts.env?.PNPM_CONFIG_USERCONFIG).toMatch(USERCONFIG_PATTERN);
|
|
287
|
+
// npm's debug log is routed into `<app>/.superblocks/logs` so a failed
|
|
288
|
+
// install in the live-edit pod leaves a collectable log.
|
|
289
|
+
expect(opts.env?.NPM_CONFIG_LOGS_DIR).toBe("/tmp/app/.superblocks/logs");
|
|
290
|
+
// Argv carries the baseline npm flags only — no --userconfig (pnpm
|
|
291
|
+
// would reject it; env is the mechanism instead).
|
|
292
|
+
const args = getResolveCommandArgs();
|
|
293
|
+
expect(args.some((a) => a.startsWith("--userconfig="))).toBe(false);
|
|
294
|
+
expect(args).toContain("--fund=false");
|
|
295
|
+
expect(args).toContain("--audit=false");
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("sets both NPM_CONFIG_USERCONFIG and PNPM_CONFIG_USERCONFIG under pnpm", async () => {
|
|
299
|
+
detectMock.mockResolvedValue({
|
|
300
|
+
name: "pnpm",
|
|
301
|
+
agent: "pnpm",
|
|
302
|
+
version: "11.0.0",
|
|
303
|
+
});
|
|
304
|
+
resolveCommandMock.mockImplementation(
|
|
305
|
+
(_agent: string, _action: string, args: string[]) => ({
|
|
306
|
+
command: "pnpm",
|
|
307
|
+
args: ["install", ...args],
|
|
308
|
+
}),
|
|
309
|
+
);
|
|
310
|
+
const { installPackages } = await import("./dev.mjs");
|
|
311
|
+
const client = fakeRegistryClient(notConfigured(undefined));
|
|
312
|
+
|
|
313
|
+
await installPackages("/tmp/app", mockLogger as never, client);
|
|
314
|
+
|
|
315
|
+
const opts = getExecOptions();
|
|
316
|
+
expect(opts.env?.NPM_CONFIG_USERCONFIG).toMatch(USERCONFIG_PATTERN);
|
|
317
|
+
expect(opts.env?.PNPM_CONFIG_USERCONFIG).toMatch(USERCONFIG_PATTERN);
|
|
318
|
+
const args = getResolveCommandArgs();
|
|
319
|
+
expect(args.some((a) => a.startsWith("--userconfig="))).toBe(false);
|
|
320
|
+
expect(args).not.toContain("--audit=false");
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it("sets the userconfig env alongside --ignore-scripts when policy is restrictive", async () => {
|
|
324
|
+
// The install-scripts policy gate (argv) and the userconfig pin
|
|
325
|
+
// (env) are independent knobs that must co-exist.
|
|
326
|
+
const { installPackages } = await import("./dev.mjs");
|
|
327
|
+
const client = fakeRegistryClient(notConfigured(false));
|
|
328
|
+
|
|
329
|
+
await installPackages("/tmp/app", mockLogger as never, client);
|
|
330
|
+
|
|
331
|
+
const args = getResolveCommandArgs();
|
|
332
|
+
expect(args).toContain("--ignore-scripts");
|
|
333
|
+
const opts = getExecOptions();
|
|
334
|
+
expect(opts.env?.NPM_CONFIG_USERCONFIG).toMatch(USERCONFIG_PATTERN);
|
|
335
|
+
expect(opts.env?.PNPM_CONFIG_USERCONFIG).toMatch(USERCONFIG_PATTERN);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// PATH-preservation coverage lives in two places that don't trip the
|
|
339
|
+
// turbo `no-undeclared-env-vars` lint:
|
|
340
|
+
// - `buildInstallEnv`'s "accepts a custom base env" test verifies
|
|
341
|
+
// the merge semantics directly without referencing `process.env`.
|
|
342
|
+
// - The real-subprocess integration test
|
|
343
|
+
// (`userconfig-env.integration.test.mts`) would fail to resolve
|
|
344
|
+
// `npm`/`pnpm` on PATH if the overlay clobbered it.
|
|
345
|
+
});
|