@specific.dev/spectest 0.131.1 → 0.131.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/android.d.ts +2 -0
- package/dist/components/android.js +2 -1
- package/dist/components/apple.d.ts +1 -0
- package/dist/components/emulate/service.d.ts +1 -0
- package/dist/components/emulate/service.js +2 -1
- package/dist/components/github.d.ts +1 -0
- package/dist/components/google.d.ts +1 -0
- package/dist/components/microsoft.d.ts +1 -0
- package/dist/components/okta.d.ts +1 -0
- package/dist/components/s3.d.ts +1 -0
- package/dist/components/s3.js +2 -1
- package/package.json +1 -1
- package/src/components/android.test.ts +14 -0
- package/src/components/android.ts +2 -1
- package/src/components/build-reach.test.ts +48 -0
- package/src/components/electron.test.ts +21 -1
- package/src/components/emulate/service.ts +2 -1
- package/src/components/expo.test.ts +16 -0
- package/src/components/s3.ts +2 -1
- package/src/components/supabase-functions-reach.test.ts +18 -0
|
@@ -42,9 +42,11 @@ export declare function android(options: AndroidOptions): import("../index.js").
|
|
|
42
42
|
type: "registry";
|
|
43
43
|
reference: string;
|
|
44
44
|
content?: undefined;
|
|
45
|
+
exclude?: undefined;
|
|
45
46
|
} | {
|
|
46
47
|
type: "dockerfile";
|
|
47
48
|
content: string;
|
|
49
|
+
exclude: string[];
|
|
48
50
|
reference?: undefined;
|
|
49
51
|
};
|
|
50
52
|
command: string;
|
|
@@ -70,9 +70,10 @@ export function android(options) {
|
|
|
70
70
|
},
|
|
71
71
|
},
|
|
72
72
|
tools: {
|
|
73
|
+
// Tools are SDK-owned; the APK is a separate input below.
|
|
73
74
|
image: options.toolsImage
|
|
74
75
|
? { type: "registry", reference: options.toolsImage }
|
|
75
|
-
: { type: "dockerfile", content: ANDROID_TOOLS_DOCKERFILE },
|
|
76
|
+
: { type: "dockerfile", content: ANDROID_TOOLS_DOCKERFILE, exclude: ["**"] },
|
|
76
77
|
command: "node /spectest-android.mjs",
|
|
77
78
|
files: [
|
|
78
79
|
{
|
|
@@ -98,7 +98,8 @@ RUN echo '{"name":"spectest-provider","private":true}' > package.json \\
|
|
|
98
98
|
* entry, and a provider can claim four names. */
|
|
99
99
|
export function emulatorService(spec) {
|
|
100
100
|
const service = {
|
|
101
|
-
image
|
|
101
|
+
// The image installs packages; its entry script is injected via files.
|
|
102
|
+
image: { type: "dockerfile", content: DOCKERFILE, exclude: ["**"] },
|
|
102
103
|
command: "bun /srv/auth.mjs",
|
|
103
104
|
files: [{ path: "/srv/auth.mjs", content: ENTRY_SCRIPT }],
|
|
104
105
|
env: { SPECTEST_AUTH_CONFIG: JSON.stringify({ port: PORT, providers: [spec] }) },
|
package/dist/components/s3.d.ts
CHANGED
package/dist/components/s3.js
CHANGED
|
@@ -61,7 +61,8 @@ export function s3(opts = {}) {
|
|
|
61
61
|
const secretAccessKey = opts.secretAccessKey ?? "s3";
|
|
62
62
|
const region = opts.region ?? "us-east-1";
|
|
63
63
|
return {
|
|
64
|
-
|
|
64
|
+
// All sources are embedded above; no project file is a build input.
|
|
65
|
+
image: { type: "dockerfile", content: S3_DOCKERFILE, exclude: ["**"] },
|
|
65
66
|
ports: [S3_PORT],
|
|
66
67
|
...(opts.env ? { env: opts.env } : {}),
|
|
67
68
|
...(opts.hosts
|
package/package.json
CHANGED
|
@@ -3,6 +3,8 @@ import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { android } from "./android.js";
|
|
6
|
+
import { serviceDockerignore } from "../harness/build-context.js";
|
|
7
|
+
import { compactReach, projectReach, reachContains } from "../harness/reach.js";
|
|
6
8
|
|
|
7
9
|
test("Android expands into two VM services with an APK input and private Appium/ADB addresses", async () => {
|
|
8
10
|
const dir = mkdtempSync(join(tmpdir(), "spectest-android-")),
|
|
@@ -45,6 +47,18 @@ test("Android expands into two VM services with an APK input and private Appium/
|
|
|
45
47
|
expect(tools.env?.ANDROID_SHOW_TOUCHES).toBe("1");
|
|
46
48
|
expect(tools.env?.ANDROID_CA_PATH).toBe(process.env.SPECTEST_CA_PATH ?? "/etc/spectest/ca.crt");
|
|
47
49
|
expect(tools.inputs).toContain("app.apk");
|
|
50
|
+
const projectFiles = ["app.apk", "terraform/budgets.tf", "k8s/nodepools/README.md"];
|
|
51
|
+
const sources = [{
|
|
52
|
+
service: "phone",
|
|
53
|
+
context: tools.image.context ?? ".",
|
|
54
|
+
ignore: serviceDockerignore(null, tools.image.exclude),
|
|
55
|
+
inputs: tools.inputs,
|
|
56
|
+
}];
|
|
57
|
+
const reached = projectReach(projectFiles, sources);
|
|
58
|
+
expect(reached).toEqual(["app.apk"]);
|
|
59
|
+
const rules = { version: 1, sources, prefixes: compactReach(projectFiles, reached) };
|
|
60
|
+
expect(reachContains(rules, "app.apk")).toBe(true);
|
|
61
|
+
expect(reachContains(rules, "k8s/new-nodepool.yaml")).toBe(false);
|
|
48
62
|
expect(tools.volumes).toContainEqual({
|
|
49
63
|
source: apk,
|
|
50
64
|
target: "/spectest/app.apk",
|
|
@@ -101,9 +101,10 @@ export function android(options: AndroidOptions) {
|
|
|
101
101
|
},
|
|
102
102
|
},
|
|
103
103
|
tools: {
|
|
104
|
+
// Tools are SDK-owned; the APK is a separate input below.
|
|
104
105
|
image: options.toolsImage
|
|
105
106
|
? { type: "registry" as const, reference: options.toolsImage }
|
|
106
|
-
: { type: "dockerfile" as const, content: ANDROID_TOOLS_DOCKERFILE },
|
|
107
|
+
: { type: "dockerfile" as const, content: ANDROID_TOOLS_DOCKERFILE, exclude: ["**"] },
|
|
107
108
|
command: "node /spectest-android.mjs",
|
|
108
109
|
files: [
|
|
109
110
|
{
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { defineEnvironment } from "../index.js";
|
|
3
|
+
import { serviceDockerignore } from "../harness/build-context.js";
|
|
4
|
+
import { compactReach, projectReach, reachContains } from "../harness/reach.js";
|
|
5
|
+
import { s3 } from "./s3.js";
|
|
6
|
+
import { google } from "./google.js";
|
|
7
|
+
import { github } from "./github.js";
|
|
8
|
+
import { apple } from "./apple.js";
|
|
9
|
+
import { microsoft } from "./microsoft.js";
|
|
10
|
+
import { okta } from "./okta.js";
|
|
11
|
+
|
|
12
|
+
const unrelatedFiles = [
|
|
13
|
+
"terraform/budgets.tf",
|
|
14
|
+
"k8s/nodepools/README.md",
|
|
15
|
+
"docs/guide.md",
|
|
16
|
+
"packages/app/index.ts",
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
for (const [name, component] of Object.entries({
|
|
20
|
+
s3: s3(),
|
|
21
|
+
google: google(),
|
|
22
|
+
github: github(),
|
|
23
|
+
apple: apple(),
|
|
24
|
+
microsoft: microsoft(),
|
|
25
|
+
okta: okta({ domain: "example.okta.com" }),
|
|
26
|
+
})) {
|
|
27
|
+
test(`${name}'s SDK-owned build does not make project files reachable`, () => {
|
|
28
|
+
const services = defineEnvironment({ name, services: { component } }).config.services;
|
|
29
|
+
for (const projectIgnore of [null, "**\n!terraform/**\n!packages/**"]) {
|
|
30
|
+
const sources = Object.entries(services).map(([service, svc]) => {
|
|
31
|
+
expect(svc.image.type).toBe("dockerfile");
|
|
32
|
+
if (svc.image.type !== "dockerfile") throw new Error("expected a built image");
|
|
33
|
+
return {
|
|
34
|
+
service,
|
|
35
|
+
context: svc.image.context ?? ".",
|
|
36
|
+
// A project's negations must not re-include files in this build.
|
|
37
|
+
ignore: serviceDockerignore(projectIgnore, svc.image.exclude),
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
const files = projectReach(unrelatedFiles, sources);
|
|
41
|
+
expect(files).toEqual([]);
|
|
42
|
+
const rules = { version: 1, sources, prefixes: compactReach(unrelatedFiles, files) };
|
|
43
|
+
for (const file of [...unrelatedFiles, "k8s/new-nodepool.yaml"]) {
|
|
44
|
+
expect(reachContains(rules, file)).toBe(false);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test';
|
|
2
2
|
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
4
|
+
import { join, relative } from 'node:path';
|
|
5
5
|
import { electron } from './electron.js';
|
|
6
6
|
import { desktopApp, isDesktopApp } from '../desktop.js';
|
|
7
|
+
import { serviceDockerignore } from '../harness/build-context.js';
|
|
8
|
+
import { projectReach, reachContains } from '../harness/reach.js';
|
|
7
9
|
|
|
8
10
|
function fixture(run: (directory: string) => void) {
|
|
9
11
|
const directory = mkdtempSync(join(tmpdir(), 'spectest-electron-'));
|
|
@@ -57,6 +59,24 @@ describe('Electron component in a monorepo', () => {
|
|
|
57
59
|
expect(content).toContain('RUN cd apps/desktop && npm run build\n');
|
|
58
60
|
expect(service.env?.SPECTEST_ELECTRON_APP_DIR).toBe('/app/apps/desktop');
|
|
59
61
|
}));
|
|
62
|
+
test('the default app context excludes siblings while an explicit monorepo context honors ignore rules', () => monorepo(root => {
|
|
63
|
+
writeFileSync(join(root, 'apps', 'desktop', 'package.json'), JSON.stringify({ devDependencies: { electron: '44.0.0' } }));
|
|
64
|
+
for (const context of [undefined, root]) {
|
|
65
|
+
const image = electron({ appDir: join(root, 'apps/desktop'), context }).image;
|
|
66
|
+
// Fixtures use absolute paths; the guest receives project-relative paths.
|
|
67
|
+
const sources = [{
|
|
68
|
+
context: relative(root, image.context) || '.',
|
|
69
|
+
ignore: serviceDockerignore('docs\nterraform', image.exclude),
|
|
70
|
+
}];
|
|
71
|
+
const files = ['apps/desktop/main.ts', 'apps/desktop/node_modules/x/index.js', 'packages/shared/index.ts', 'docs/guide.md', 'terraform/budgets.tf'];
|
|
72
|
+
expect(projectReach(files, sources)).toEqual(context
|
|
73
|
+
? ['apps/desktop/main.ts', 'packages/shared/index.ts']
|
|
74
|
+
: ['apps/desktop/main.ts']);
|
|
75
|
+
const rules = { version: 1, sources, prefixes: [] };
|
|
76
|
+
expect(reachContains(rules, 'apps/desktop/new-window.ts')).toBe(true);
|
|
77
|
+
expect(reachContains(rules, 'docs/new-guide.md')).toBe(false);
|
|
78
|
+
}
|
|
79
|
+
}));
|
|
60
80
|
test('accepts electron declared at the context root only', () => monorepo(root => {
|
|
61
81
|
expect(() => electron({ appDir: join(root, 'apps/desktop'), context: root })).not.toThrow();
|
|
62
82
|
writeFileSync(join(root, 'package.json'), JSON.stringify({ workspaces: ['apps/*'] }));
|
|
@@ -174,7 +174,8 @@ RUN echo '{"name":"spectest-provider","private":true}' > package.json \\
|
|
|
174
174
|
* entry, and a provider can claim four names. */
|
|
175
175
|
export function emulatorService(spec: ProviderSpec) {
|
|
176
176
|
const service = {
|
|
177
|
-
image
|
|
177
|
+
// The image installs packages; its entry script is injected via files.
|
|
178
|
+
image: { type: "dockerfile" as const, content: DOCKERFILE, exclude: ["**"] },
|
|
178
179
|
command: "bun /srv/auth.mjs",
|
|
179
180
|
files: [{ path: "/srv/auth.mjs", content: ENTRY_SCRIPT }],
|
|
180
181
|
env: { SPECTEST_AUTH_CONFIG: JSON.stringify({ port: PORT, providers: [spec] }) },
|
|
@@ -11,6 +11,8 @@ import { join } from "node:path";
|
|
|
11
11
|
|
|
12
12
|
import { expo, nodeMajorFromRange } from "./expo.js";
|
|
13
13
|
import type { ServiceConfig } from "../index.js";
|
|
14
|
+
import { serviceDockerignore } from "../harness/build-context.js";
|
|
15
|
+
import { projectReach, reachContains } from "../harness/reach.js";
|
|
14
16
|
|
|
15
17
|
let root = "";
|
|
16
18
|
const prevWorkspace = process.env.SPECTEST_WORKSPACE;
|
|
@@ -53,6 +55,20 @@ function lineOf(text: string, needle: string): number {
|
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
describe("expo() Dockerfile caching", () => {
|
|
58
|
+
test("the app context excludes sibling infrastructure and docs but includes new app code", () => {
|
|
59
|
+
app({ "package.json": pkg(), "App.tsx": "export default 1" }, "apps/mobile");
|
|
60
|
+
const image = expo({ appDir: "apps/mobile" }).image;
|
|
61
|
+
if (image.type !== "dockerfile") throw new Error("expected a built image");
|
|
62
|
+
const sources = [{ context: image.context, ignore: serviceDockerignore(null, image.exclude) }];
|
|
63
|
+
expect(projectReach([
|
|
64
|
+
"apps/mobile/package.json", "apps/mobile/App.tsx", "apps/mobile/node_modules/x/index.js",
|
|
65
|
+
"apps/mobile/dist/index.html", "terraform/budgets.tf", "docs/guide.md", "apps/other/index.ts",
|
|
66
|
+
], sources)).toEqual(["apps/mobile/App.tsx", "apps/mobile/package.json"]);
|
|
67
|
+
const rules = { version: 1, sources, prefixes: [] };
|
|
68
|
+
expect(reachContains(rules, "apps/mobile/new-screen.tsx")).toBe(true);
|
|
69
|
+
expect(reachContains(rules, "k8s/new-nodepool.yaml")).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
|
|
56
72
|
test("installs from the manifests before it copies the source", () => {
|
|
57
73
|
app({ "package.json": pkg(), "package-lock.json": "{}", "App.js": "x" });
|
|
58
74
|
const df = dockerfile(expo());
|
package/src/components/s3.ts
CHANGED
|
@@ -108,7 +108,8 @@ export function s3(opts: S3Options = {}) {
|
|
|
108
108
|
const region = opts.region ?? "us-east-1";
|
|
109
109
|
|
|
110
110
|
return {
|
|
111
|
-
|
|
111
|
+
// All sources are embedded above; no project file is a build input.
|
|
112
|
+
image: { type: "dockerfile", content: S3_DOCKERFILE, exclude: ["**"] },
|
|
112
113
|
ports: [S3_PORT],
|
|
113
114
|
...(opts.env ? { env: opts.env } : {}),
|
|
114
115
|
...(opts.hosts
|
|
@@ -4,6 +4,8 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
|
|
6
6
|
import { excludeAllBut, functionsReach, importMapLocalTargets, importSpecifiers } from "./supabase-functions-reach.js";
|
|
7
|
+
import { serviceDockerignore } from "../harness/build-context.js";
|
|
8
|
+
import { projectReach, reachContains } from "../harness/reach.js";
|
|
7
9
|
|
|
8
10
|
let root: string;
|
|
9
11
|
|
|
@@ -82,6 +84,22 @@ describe("supabase() wiring", () => {
|
|
|
82
84
|
expect(fn.image.exclude?.at(-1)).toBe("**/.venv/**");
|
|
83
85
|
expect(fn.volumes?.[0]).toMatchObject({ source: root, reachExempt: true });
|
|
84
86
|
expect((parts.db as { inputs?: readonly string[] }).inputs).toEqual(["supabase"]);
|
|
87
|
+
const sources = [
|
|
88
|
+
{ context: fn.image.context, ignore: serviceDockerignore(null, fn.image.exclude) },
|
|
89
|
+
{ inputs: [...(parts.db as { inputs: string[] }).inputs] },
|
|
90
|
+
];
|
|
91
|
+
expect(projectReach([
|
|
92
|
+
"supabase/config.toml", "supabase/migrations/001.sql", "supabase/functions/hello/index.ts",
|
|
93
|
+
"packages/shared/src/greet.ts", "lib/db/client.ts", "packages/unrelated/index.ts",
|
|
94
|
+
"docs/guide.md", "site/app/page.tsx", "terraform/budgets.tf",
|
|
95
|
+
], sources)).toEqual([
|
|
96
|
+
"lib/db/client.ts", "packages/shared/src/greet.ts", "supabase/config.toml",
|
|
97
|
+
"supabase/functions/hello/index.ts", "supabase/migrations/001.sql",
|
|
98
|
+
]);
|
|
99
|
+
const rules = { version: 1, sources, prefixes: [] };
|
|
100
|
+
expect(reachContains(rules, "packages/shared/src/new-helper.ts")).toBe(true);
|
|
101
|
+
expect(reachContains(rules, "supabase/migrations/002.sql")).toBe(true);
|
|
102
|
+
expect(reachContains(rules, "k8s/new-nodepool.yaml")).toBe(false);
|
|
85
103
|
} finally {
|
|
86
104
|
if (prev === undefined) delete process.env.SPECTEST_WORKSPACE;
|
|
87
105
|
else process.env.SPECTEST_WORKSPACE = prev;
|