@ykdz/template 0.0.32 → 0.0.35
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/node_modules/@ykdz/template-builtin-presets/dist/src/foundation.d.ts +2 -0
- package/node_modules/@ykdz/template-builtin-presets/dist/src/foundation.js +40 -14
- package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/development-container.d.ts +1 -0
- package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/development-container.js +17 -0
- package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/vue.js +1 -1
- package/node_modules/@ykdz/template-builtin-presets/dist/src/vike-app/definition.js +9 -10
- package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-app/definition.js +6 -1
- package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-hono-app/definition.js +6 -1
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-deployment-diagnostics.yml +81 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-deployment.yml +55 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-diagnostics.yml +64 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check.yml +24 -5
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/gitignore +1 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/shared/devcontainer/github-cli.Dockerfile +9 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db/src/readiness.ts +1 -1
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db/test/global-setup.ts +21 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db/tsconfig.json +6 -1
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db/vitest.config.ts +7 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db-migrations/drizzle.config.ts +5 -1
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/playwright.config.ts +103 -22
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/test/playwright-teardown.ts +12 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/vite.config.ts +39 -1
- package/node_modules/@ykdz/template-builtin-presets/templates/vue-app/playwright.config.ts +50 -8
- package/node_modules/@ykdz/template-builtin-presets/templates/vue-hono-app/web/playwright.config.ts +61 -8
- package/node_modules/@ykdz/template-core/dist/ci-diagnostic-artifact.d.ts +19 -0
- package/node_modules/@ykdz/template-core/dist/ci-diagnostic-artifact.js +68 -0
- package/node_modules/@ykdz/template-core/dist/package-contribution.d.ts +3 -0
- package/node_modules/@ykdz/template-core/dist/package-contribution.js +7 -0
- package/node_modules/@ykdz/template-core/dist/project-github.d.ts +50 -6
- package/node_modules/@ykdz/template-core/dist/project-github.js +83 -47
- package/node_modules/@ykdz/template-core/dist/template-boundary-check.d.ts +15 -1
- package/node_modules/@ykdz/template-core/dist/template-boundary-check.js +162 -1
- package/package.json +1 -1
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-playwright.yml +0 -20
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-rust.yml +0 -23
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check.dynamic.template +0 -19
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db-migrations/scripts/prepare-database.ts +0 -45
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/scripts/run-playwright.ts +0 -151
- package/node_modules/@ykdz/template-builtin-presets/templates/vue-app/scripts/run-playwright.ts +0 -62
- package/node_modules/@ykdz/template-builtin-presets/templates/vue-hono-app/web/scripts/run-playwright.ts +0 -103
package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/playwright.config.ts
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { rmSync } from "node:fs";
|
|
3
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { createServer } from "node:net";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
1
8
|
import { defineConfig, devices } from "@playwright/test";
|
|
2
9
|
|
|
3
10
|
const externalBaseUrlName = "PLAYWRIGHT_EXTERNAL_BASE_URL";
|
|
11
|
+
const externalReadinessTimeoutMs = 30_000;
|
|
12
|
+
const webRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const migrationsRoot = path.resolve(webRoot, "../../packages/db-migrations");
|
|
14
|
+
const databaseFile = path.join(webRoot, "node_modules/.tmp/e2e.sqlite");
|
|
15
|
+
const stateFile = path.join(webRoot, "node_modules/.tmp/playwright-port");
|
|
4
16
|
|
|
5
17
|
function externalBaseUrl(): string | undefined {
|
|
6
18
|
if (!Object.hasOwn(process.env, externalBaseUrlName)) {
|
|
@@ -26,31 +38,98 @@ function externalBaseUrl(): string | undefined {
|
|
|
26
38
|
return url.toString();
|
|
27
39
|
}
|
|
28
40
|
|
|
29
|
-
function
|
|
30
|
-
return
|
|
41
|
+
async function availablePort(): Promise<string> {
|
|
42
|
+
return await new Promise((resolve, reject) => {
|
|
43
|
+
const server = createServer();
|
|
44
|
+
|
|
45
|
+
server.once("error", reject);
|
|
46
|
+
server.listen(0, "127.0.0.1", () => {
|
|
47
|
+
const address = server.address();
|
|
48
|
+
|
|
49
|
+
if (typeof address !== "object" || address === null) {
|
|
50
|
+
server.close(() => {
|
|
51
|
+
reject(new Error("Could not allocate a Playwright web port"));
|
|
52
|
+
});
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const port = String(address.port);
|
|
57
|
+
server.close((error) => {
|
|
58
|
+
if (error) {
|
|
59
|
+
reject(error);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
resolve(port);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
});
|
|
31
67
|
}
|
|
32
68
|
|
|
33
|
-
function
|
|
34
|
-
const
|
|
69
|
+
async function awaitExternalService(baseUrl: string): Promise<void> {
|
|
70
|
+
const deadline = Date.now() + externalReadinessTimeoutMs;
|
|
35
71
|
|
|
36
|
-
|
|
37
|
-
|
|
72
|
+
while (Date.now() < deadline) {
|
|
73
|
+
try {
|
|
74
|
+
const response = await fetch(baseUrl, {
|
|
75
|
+
signal: AbortSignal.timeout(2_000),
|
|
76
|
+
});
|
|
77
|
+
if (response.ok) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
} catch {
|
|
81
|
+
// The externally managed service may still be starting.
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
38
85
|
}
|
|
39
86
|
|
|
40
|
-
|
|
87
|
+
throw new Error(
|
|
88
|
+
`External Playwright service at ${baseUrl} was not ready within ${externalReadinessTimeoutMs}ms`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function sharedPort(envName: string): Promise<{
|
|
93
|
+
readonly port: string;
|
|
94
|
+
readonly prepared: boolean;
|
|
95
|
+
}> {
|
|
96
|
+
const envPort = process.env[envName];
|
|
97
|
+
if (envPort !== undefined) {
|
|
98
|
+
return { port: envPort, prepared: false };
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
return { port: await readFile(stateFile, "utf8"), prepared: false };
|
|
102
|
+
} catch {
|
|
103
|
+
const port = await availablePort();
|
|
104
|
+
await mkdir(path.dirname(stateFile), { recursive: true });
|
|
105
|
+
await writeFile(stateFile, port, "utf8");
|
|
106
|
+
return { port, prepared: true };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function prepareLocalDatabase(): void {
|
|
111
|
+
rmSync(databaseFile, { force: true });
|
|
112
|
+
execFileSync("pnpm", ["run", "db:push"], {
|
|
113
|
+
cwd: migrationsRoot,
|
|
114
|
+
env: { ...process.env, DATABASE_FILE: databaseFile },
|
|
115
|
+
stdio: "inherit",
|
|
116
|
+
});
|
|
41
117
|
}
|
|
42
118
|
|
|
43
119
|
const externalServiceUrl = externalBaseUrl();
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
120
|
+
let previewUrl: string;
|
|
121
|
+
|
|
122
|
+
if (externalServiceUrl === undefined) {
|
|
123
|
+
const { port, prepared } = await sharedPort("PLAYWRIGHT_WEB_PORT");
|
|
124
|
+
previewUrl = `http://127.0.0.1:${port}`;
|
|
125
|
+
process.env.DATABASE_FILE = databaseFile;
|
|
126
|
+
process.env.PORT = port;
|
|
127
|
+
if (prepared) {
|
|
128
|
+
prepareLocalDatabase();
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
previewUrl = externalServiceUrl;
|
|
132
|
+
await awaitExternalService(externalServiceUrl);
|
|
54
133
|
}
|
|
55
134
|
|
|
56
135
|
export default defineConfig({
|
|
@@ -60,16 +139,18 @@ export default defineConfig({
|
|
|
60
139
|
baseURL: previewUrl,
|
|
61
140
|
trace: "retain-on-failure",
|
|
62
141
|
},
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
142
|
+
reporter: [["list"], ["html"]],
|
|
143
|
+
globalTeardown: "./test/playwright-teardown.ts",
|
|
144
|
+
...(externalServiceUrl === undefined
|
|
145
|
+
? {
|
|
66
146
|
webServer: {
|
|
67
|
-
command:
|
|
147
|
+
command: "node dist/server/index.mjs",
|
|
68
148
|
reuseExistingServer: !process.env.CI,
|
|
69
149
|
timeout: 60_000,
|
|
70
150
|
url: previewUrl,
|
|
71
151
|
},
|
|
72
|
-
}
|
|
152
|
+
}
|
|
153
|
+
: {}),
|
|
73
154
|
projects: [
|
|
74
155
|
{
|
|
75
156
|
name: "chromium",
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { rmSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const webRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
|
|
7
|
+
export default function teardown(): void {
|
|
8
|
+
rmSync(path.join(webRoot, "node_modules/.tmp/e2e.sqlite"), { force: true });
|
|
9
|
+
rmSync(path.join(webRoot, "node_modules/.tmp/playwright-port"), {
|
|
10
|
+
force: true,
|
|
11
|
+
});
|
|
12
|
+
}
|
|
@@ -1,12 +1,50 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
1
5
|
import tailwindcss from "@tailwindcss/vite";
|
|
2
6
|
import vue from "@vitejs/plugin-vue";
|
|
3
7
|
import telefunc from "telefunc/vite";
|
|
4
8
|
import vike from "vike/plugin";
|
|
5
9
|
import { defineConfig, type PluginOption } from "vite";
|
|
6
10
|
|
|
11
|
+
const webRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const migrationsRoot = path.resolve(webRoot, "../../packages/db-migrations");
|
|
13
|
+
const databaseRoot = path.resolve(webRoot, "../../packages/db");
|
|
14
|
+
const databaseFile = path.join(webRoot, "data/app.sqlite");
|
|
15
|
+
|
|
7
16
|
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- telefunc/vite currently types its plugin factory as any.
|
|
8
17
|
const telefuncPlugin = telefunc() as PluginOption;
|
|
9
18
|
|
|
19
|
+
async function prepareDatabase(): Promise<void> {
|
|
20
|
+
const env = { ...process.env, DATABASE_FILE: databaseFile };
|
|
21
|
+
process.env.DATABASE_FILE = databaseFile;
|
|
22
|
+
execFileSync("pnpm", ["run", "db:push"], {
|
|
23
|
+
cwd: migrationsRoot,
|
|
24
|
+
env,
|
|
25
|
+
stdio: "inherit",
|
|
26
|
+
});
|
|
27
|
+
execFileSync("pnpm", ["run", "db:seed:example"], {
|
|
28
|
+
cwd: databaseRoot,
|
|
29
|
+
env,
|
|
30
|
+
stdio: "inherit",
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function databasePreparationPlugin(): PluginOption {
|
|
35
|
+
return {
|
|
36
|
+
name: "database-preparation",
|
|
37
|
+
configureServer: () => prepareDatabase(),
|
|
38
|
+
configurePreviewServer: () => prepareDatabase(),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
10
42
|
export default defineConfig({
|
|
11
|
-
plugins: [
|
|
43
|
+
plugins: [
|
|
44
|
+
vike(),
|
|
45
|
+
telefuncPlugin,
|
|
46
|
+
vue(),
|
|
47
|
+
tailwindcss(),
|
|
48
|
+
databasePreparationPlugin(),
|
|
49
|
+
],
|
|
12
50
|
});
|
|
@@ -1,24 +1,66 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { createServer } from "node:net";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
1
6
|
import { defineConfig, devices } from "@playwright/test";
|
|
2
7
|
|
|
3
|
-
|
|
4
|
-
|
|
8
|
+
const webRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const stateFile = path.join(webRoot, "node_modules/.tmp/playwright-port");
|
|
5
10
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
11
|
+
async function availablePort(): Promise<string> {
|
|
12
|
+
return await new Promise((resolve, reject) => {
|
|
13
|
+
const server = createServer();
|
|
14
|
+
|
|
15
|
+
server.once("error", reject);
|
|
16
|
+
server.listen(0, "127.0.0.1", () => {
|
|
17
|
+
const address = server.address();
|
|
18
|
+
|
|
19
|
+
if (typeof address !== "object" || address === null) {
|
|
20
|
+
server.close(() => {
|
|
21
|
+
reject(new Error("Could not allocate a Playwright web port"));
|
|
22
|
+
});
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const port = String(address.port);
|
|
27
|
+
server.close((error) => {
|
|
28
|
+
if (error) {
|
|
29
|
+
reject(error);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
9
32
|
|
|
10
|
-
|
|
33
|
+
resolve(port);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function sharedPort(envName: string): Promise<string> {
|
|
40
|
+
const envPort = process.env[envName];
|
|
41
|
+
if (envPort !== undefined) {
|
|
42
|
+
return envPort;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
return await readFile(stateFile, "utf8");
|
|
46
|
+
} catch {
|
|
47
|
+
const port = await availablePort();
|
|
48
|
+
await mkdir(path.dirname(stateFile), { recursive: true });
|
|
49
|
+
await writeFile(stateFile, port, "utf8");
|
|
50
|
+
return port;
|
|
51
|
+
}
|
|
11
52
|
}
|
|
12
53
|
|
|
13
|
-
const previewPort =
|
|
54
|
+
const previewPort = await sharedPort("PLAYWRIGHT_WEB_PORT");
|
|
14
55
|
const previewUrl = `http://127.0.0.1:${previewPort}`;
|
|
15
56
|
|
|
16
57
|
export default defineConfig({
|
|
17
58
|
testDir: "./test/e2e",
|
|
18
59
|
use: {
|
|
19
60
|
baseURL: previewUrl,
|
|
20
|
-
trace: "on-
|
|
61
|
+
trace: "retain-on-failure",
|
|
21
62
|
},
|
|
63
|
+
reporter: [["list"], ["html"]],
|
|
22
64
|
webServer: {
|
|
23
65
|
command: `pnpm run preview --host 127.0.0.1 --port ${previewPort}`,
|
|
24
66
|
reuseExistingServer: !process.env.CI,
|
package/node_modules/@ykdz/template-builtin-presets/templates/vue-hono-app/web/playwright.config.ts
CHANGED
|
@@ -1,17 +1,68 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { createServer } from "node:net";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
1
6
|
import { defineConfig, devices } from "@playwright/test";
|
|
2
7
|
|
|
3
|
-
|
|
4
|
-
|
|
8
|
+
const webRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const stateFile = path.join(webRoot, "node_modules/.tmp/playwright-ports");
|
|
5
10
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
11
|
+
async function availablePort(): Promise<string> {
|
|
12
|
+
return await new Promise((resolve, reject) => {
|
|
13
|
+
const server = createServer();
|
|
14
|
+
|
|
15
|
+
server.once("error", reject);
|
|
16
|
+
server.listen(0, "127.0.0.1", () => {
|
|
17
|
+
const address = server.address();
|
|
18
|
+
|
|
19
|
+
if (typeof address !== "object" || address === null) {
|
|
20
|
+
server.close(() => {
|
|
21
|
+
reject(new Error("Could not allocate a Playwright web port"));
|
|
22
|
+
});
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const port = String(address.port);
|
|
27
|
+
server.close((error) => {
|
|
28
|
+
if (error) {
|
|
29
|
+
reject(error);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
9
32
|
|
|
10
|
-
|
|
33
|
+
resolve(port);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function sharedPorts(): Promise<{
|
|
40
|
+
readonly api: string;
|
|
41
|
+
readonly web: string;
|
|
42
|
+
}> {
|
|
43
|
+
const envApi = process.env.PLAYWRIGHT_API_PORT;
|
|
44
|
+
const envWeb = process.env.PLAYWRIGHT_WEB_PORT;
|
|
45
|
+
if (envApi !== undefined && envWeb !== undefined) {
|
|
46
|
+
return { api: envApi, web: envWeb };
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const parsed = JSON.parse(await readFile(stateFile, "utf8")) as {
|
|
50
|
+
readonly api: string;
|
|
51
|
+
readonly web: string;
|
|
52
|
+
};
|
|
53
|
+
return parsed;
|
|
54
|
+
} catch {
|
|
55
|
+
const ports = {
|
|
56
|
+
api: envApi ?? (await availablePort()),
|
|
57
|
+
web: envWeb ?? (await availablePort()),
|
|
58
|
+
};
|
|
59
|
+
await mkdir(path.dirname(stateFile), { recursive: true });
|
|
60
|
+
await writeFile(stateFile, JSON.stringify(ports), "utf8");
|
|
61
|
+
return ports;
|
|
62
|
+
}
|
|
11
63
|
}
|
|
12
64
|
|
|
13
|
-
const apiPort =
|
|
14
|
-
const webPort = requiredPort("PLAYWRIGHT_WEB_PORT");
|
|
65
|
+
const { api: apiPort, web: webPort } = await sharedPorts();
|
|
15
66
|
const apiBaseUrl = `http://127.0.0.1:${apiPort}`;
|
|
16
67
|
const apiHealthUrl = `${apiBaseUrl}/api/health`;
|
|
17
68
|
const webBaseUrl = `http://127.0.0.1:${webPort}`;
|
|
@@ -20,8 +71,10 @@ export default defineConfig({
|
|
|
20
71
|
testDir: "./test/e2e",
|
|
21
72
|
use: {
|
|
22
73
|
baseURL: webBaseUrl,
|
|
74
|
+
trace: "retain-on-failure",
|
|
23
75
|
...devices["Desktop Chrome"],
|
|
24
76
|
},
|
|
77
|
+
reporter: [["list"], ["html"]],
|
|
25
78
|
webServer: [
|
|
26
79
|
{
|
|
27
80
|
command: `PORT=${apiPort} pnpm --dir ../api run start`,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PackageBoundaryOwner } from "./module-graph.ts";
|
|
2
|
+
/** Closed, owner-scoped native evidence a Package Contribution may retain in CI. */
|
|
3
|
+
export type CiDiagnosticArtifactDeclaration = {
|
|
4
|
+
readonly kind: "playwright";
|
|
5
|
+
readonly owner: PackageBoundaryOwner;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Validates the intentionally closed diagnostic declaration surface. It does
|
|
9
|
+
* not accept paths: native paths are derived only after the owner is known.
|
|
10
|
+
*/
|
|
11
|
+
export declare function assertCiDiagnosticArtifactDeclaration(value: unknown): CiDiagnosticArtifactDeclaration;
|
|
12
|
+
/**
|
|
13
|
+
* Composes closed native diagnostics independently of presets and environment
|
|
14
|
+
* preparation. Equivalent declarations collapse into stable owner order.
|
|
15
|
+
*/
|
|
16
|
+
export declare function composeCiDiagnosticArtifacts(options: {
|
|
17
|
+
readonly packagePaths: readonly string[];
|
|
18
|
+
readonly declarations: readonly unknown[];
|
|
19
|
+
}): readonly CiDiagnosticArtifactDeclaration[];
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
function isRecord(value) {
|
|
2
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
function hasExactKeys(value, keys) {
|
|
5
|
+
const actual = Object.keys(value).toSorted();
|
|
6
|
+
const expected = [...keys].toSorted();
|
|
7
|
+
return (actual.length === expected.length &&
|
|
8
|
+
actual.every((key, index) => key === expected[index]));
|
|
9
|
+
}
|
|
10
|
+
const reservedWorkspaceCollections = new Set([
|
|
11
|
+
".git",
|
|
12
|
+
".github",
|
|
13
|
+
".devcontainer",
|
|
14
|
+
".template",
|
|
15
|
+
"node_modules",
|
|
16
|
+
"dist",
|
|
17
|
+
"target",
|
|
18
|
+
]);
|
|
19
|
+
function isSafePackagePath(value) {
|
|
20
|
+
return (/^[a-z0-9][a-z0-9-]*\/[a-z0-9][a-z0-9-]*$/u.test(value) &&
|
|
21
|
+
!reservedWorkspaceCollections.has(value.split("/", 1)[0]));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Validates the intentionally closed diagnostic declaration surface. It does
|
|
25
|
+
* not accept paths: native paths are derived only after the owner is known.
|
|
26
|
+
*/
|
|
27
|
+
export function assertCiDiagnosticArtifactDeclaration(value) {
|
|
28
|
+
if (!isRecord(value) || !hasExactKeys(value, ["kind", "owner"])) {
|
|
29
|
+
throw new Error("CI Diagnostic Artifact declarations may contain only kind and owner");
|
|
30
|
+
}
|
|
31
|
+
if (value.kind !== "playwright") {
|
|
32
|
+
throw new Error("Unsupported CI Diagnostic Artifact kind");
|
|
33
|
+
}
|
|
34
|
+
if (!isRecord(value.owner) || !hasExactKeys(value.owner, ["kind", "path"])) {
|
|
35
|
+
throw new Error("CI Diagnostic Artifact requires a Package Boundary owner");
|
|
36
|
+
}
|
|
37
|
+
if (value.owner.kind !== "package-boundary" ||
|
|
38
|
+
typeof value.owner.path !== "string" ||
|
|
39
|
+
!isSafePackagePath(value.owner.path)) {
|
|
40
|
+
throw new Error("CI Diagnostic Artifact owner has an unsafe Package Boundary path");
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
kind: "playwright",
|
|
44
|
+
owner: { kind: "package-boundary", path: value.owner.path },
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Composes closed native diagnostics independently of presets and environment
|
|
49
|
+
* preparation. Equivalent declarations collapse into stable owner order.
|
|
50
|
+
*/
|
|
51
|
+
export function composeCiDiagnosticArtifacts(options) {
|
|
52
|
+
for (const packagePath of options.packagePaths) {
|
|
53
|
+
if (!isSafePackagePath(packagePath)) {
|
|
54
|
+
throw new Error(`CI Diagnostic Artifact Package Boundary path is unsafe: ${packagePath}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const owners = new Set(options.packagePaths);
|
|
58
|
+
const artifacts = new Map();
|
|
59
|
+
for (const value of options.declarations) {
|
|
60
|
+
const declaration = assertCiDiagnosticArtifactDeclaration(value);
|
|
61
|
+
if (!owners.has(declaration.owner.path)) {
|
|
62
|
+
throw new Error(`CI Diagnostic Artifact owner is not a declared Package Boundary: ${declaration.owner.path}`);
|
|
63
|
+
}
|
|
64
|
+
const key = `${declaration.kind}:${declaration.owner.path}`;
|
|
65
|
+
artifacts.set(key, declaration);
|
|
66
|
+
}
|
|
67
|
+
return [...artifacts.values()].toSorted((left, right) => left.owner.path.localeCompare(right.owner.path));
|
|
68
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type CiDiagnosticArtifactDeclaration } from "./ci-diagnostic-artifact.ts";
|
|
1
2
|
import type { DevelopmentContainerToolLayer } from "./development-container-tool-layer.ts";
|
|
2
3
|
import type { EditorCustomizationCapability } from "./editor-customization.ts";
|
|
3
4
|
import type { CheckEnvironmentNeed, DeploymentEnvironmentNeed } from "./module-graph.ts";
|
|
@@ -43,6 +44,8 @@ export type PackageContribution = {
|
|
|
43
44
|
/** Typed requirements consumed by the Foundation for coordinated root outputs. */
|
|
44
45
|
readonly foundation: FoundationContribution;
|
|
45
46
|
readonly environmentNeeds: readonly CheckEnvironmentNeed[];
|
|
47
|
+
/** Closed native CI evidence owned by this Package Boundary. */
|
|
48
|
+
readonly ciDiagnosticArtifacts?: readonly CiDiagnosticArtifactDeclaration[];
|
|
46
49
|
/** Requirements prepared only by a focused deployment entrypoint. */
|
|
47
50
|
readonly deploymentEnvironmentNeeds?: readonly DeploymentEnvironmentNeed[];
|
|
48
51
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertCiDiagnosticArtifactDeclaration, } from "./ci-diagnostic-artifact.js";
|
|
1
2
|
const operationPath = (operation) => {
|
|
2
3
|
if ("to" in operation)
|
|
3
4
|
return operation.to;
|
|
@@ -20,5 +21,11 @@ export function assertPackageContribution(contribution, provenance = {}) {
|
|
|
20
21
|
: `${provenance.definitionName}: ${provenance.planner ?? "Package Contribution"} `;
|
|
21
22
|
throw new Error(`${owner}${rule}; ${contribution.definition.path} attempted ${target}`);
|
|
22
23
|
}
|
|
24
|
+
for (const artifact of contribution.ciDiagnosticArtifacts ?? []) {
|
|
25
|
+
const declaration = assertCiDiagnosticArtifactDeclaration(artifact);
|
|
26
|
+
if (declaration.owner.path !== contribution.definition.path) {
|
|
27
|
+
throw new Error(`CI Diagnostic Artifact owner must match its Package Contribution: ${contribution.definition.path}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
23
30
|
return contribution;
|
|
24
31
|
}
|
|
@@ -1,16 +1,52 @@
|
|
|
1
|
+
import { type CiDiagnosticArtifactDeclaration } from "./ci-diagnostic-artifact.ts";
|
|
1
2
|
import type { DeploymentEnvironmentNeed } from "./module-graph.ts";
|
|
2
3
|
export type CiCapability = {
|
|
3
4
|
readonly workflowName: "Check";
|
|
4
5
|
readonly jobName: "check";
|
|
6
|
+
readonly jobDisplayName: "Root Check";
|
|
5
7
|
readonly runner: "ubuntu-latest";
|
|
8
|
+
readonly timeoutMinutes: 30;
|
|
6
9
|
};
|
|
7
10
|
export type CiEnvironmentPreparation = {
|
|
8
11
|
readonly nodeFromPackageMetadata: boolean;
|
|
12
|
+
readonly packageManagerFromPackageMetadata: boolean;
|
|
13
|
+
readonly pnpmStoreCache: boolean;
|
|
9
14
|
};
|
|
10
15
|
export type PnpmTaskLayer = {
|
|
11
|
-
readonly installCommand: "pnpm install";
|
|
16
|
+
readonly installCommand: "pnpm install --frozen-lockfile";
|
|
12
17
|
readonly checkCommand: "pnpm run check";
|
|
13
18
|
};
|
|
19
|
+
export type CiCheckMatrixEntry = {
|
|
20
|
+
readonly capability: "root" | "deployment";
|
|
21
|
+
readonly jobDisplayName: "Root Check" | "Deployment Check";
|
|
22
|
+
readonly taskEntrypoint: "pnpm run check" | "pnpm run check:deployment";
|
|
23
|
+
readonly timeoutMinutes: 30 | 45;
|
|
24
|
+
readonly requiresDocker: boolean;
|
|
25
|
+
};
|
|
26
|
+
export type ProjectCheckWorkflowPlan = {
|
|
27
|
+
readonly workflowName: "Check";
|
|
28
|
+
readonly triggers: {
|
|
29
|
+
readonly pullRequest: true;
|
|
30
|
+
readonly pushBranches: readonly ["main"];
|
|
31
|
+
};
|
|
32
|
+
readonly permissions: {
|
|
33
|
+
readonly contents: "read";
|
|
34
|
+
};
|
|
35
|
+
readonly concurrency: {
|
|
36
|
+
readonly group: "${{ github.workflow }}-${{ github.ref }}";
|
|
37
|
+
readonly cancelInProgress: true;
|
|
38
|
+
};
|
|
39
|
+
readonly rootCheck: CiCapability;
|
|
40
|
+
readonly matrix: {
|
|
41
|
+
readonly failFast: false;
|
|
42
|
+
readonly include: readonly CiCheckMatrixEntry[];
|
|
43
|
+
} | undefined;
|
|
44
|
+
readonly environmentPreparation: CiEnvironmentPreparation;
|
|
45
|
+
readonly taskLayer: PnpmTaskLayer;
|
|
46
|
+
/** Actual Package Boundaries allowed to own retained native diagnostics. */
|
|
47
|
+
readonly packagePaths: readonly string[];
|
|
48
|
+
readonly diagnosticArtifacts: readonly CiDiagnosticArtifactDeclaration[];
|
|
49
|
+
};
|
|
14
50
|
export type DependencyEcosystem = "npm" | "cargo" | "github-actions" | "docker" | "rust-toolchain";
|
|
15
51
|
export type DependabotDirectory = `/${string}`;
|
|
16
52
|
export type DependencyMaintenancePolicy = {
|
|
@@ -19,19 +55,27 @@ export type DependencyMaintenancePolicy = {
|
|
|
19
55
|
readonly extraDirectories?: Partial<Record<DependencyEcosystem, readonly DependabotDirectory[]>>;
|
|
20
56
|
readonly interval: "weekly";
|
|
21
57
|
};
|
|
22
|
-
type
|
|
58
|
+
export type ProjectCheckWorkflowDiagnosticOptions = {
|
|
59
|
+
/** Package Paths from the real Project Blueprint or Package Contributions. */
|
|
60
|
+
readonly packagePaths: readonly string[];
|
|
61
|
+
readonly diagnosticArtifacts?: readonly unknown[] | undefined;
|
|
62
|
+
};
|
|
63
|
+
type ProjectCheckWorkflowOptions = ProjectCheckWorkflowDiagnosticOptions & {
|
|
23
64
|
readonly deploymentEnvironmentNeeds?: readonly DeploymentEnvironmentNeed[];
|
|
24
65
|
readonly hasDeploymentTask?: boolean | undefined;
|
|
25
66
|
readonly capability?: CiCapability | undefined;
|
|
26
67
|
readonly environmentPreparation?: Partial<CiEnvironmentPreparation> | undefined;
|
|
27
68
|
readonly taskLayer?: PnpmTaskLayer | undefined;
|
|
28
69
|
};
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
70
|
+
/** Capability facts for the Foundation-owned GitHub Actions Template Source. */
|
|
71
|
+
export declare function projectCheckWorkflowPlan(options: ProjectCheckWorkflowOptions): ProjectCheckWorkflowPlan;
|
|
72
|
+
/** Selects a complete Foundation-owned workflow Template Source. */
|
|
73
|
+
export declare function projectCheckWorkflowTemplateSource(options: ProjectCheckWorkflowDiagnosticOptions & {
|
|
32
74
|
readonly deploymentEnvironmentNeeds?: readonly DeploymentEnvironmentNeed[];
|
|
33
75
|
readonly hasDeploymentTask?: boolean | undefined;
|
|
34
|
-
}):
|
|
76
|
+
}): ".github/workflows/check.yml" | ".github/workflows/check-diagnostics.yml" | ".github/workflows/check-deployment.yml" | ".github/workflows/check-deployment-diagnostics.yml";
|
|
77
|
+
/** Limited, validated facts for static Foundation diagnostic staging. */
|
|
78
|
+
export declare function projectCheckWorkflowTemplateReplacements(options: ProjectCheckWorkflowDiagnosticOptions): Record<string, string>;
|
|
35
79
|
export declare function projectDependabotConfig(policy: DependencyMaintenancePolicy): string;
|
|
36
80
|
/** Limited substitution for the Foundation-owned Dependabot Template Source. */
|
|
37
81
|
export declare function projectDependabotTemplateReplacements(policy: DependencyMaintenancePolicy): Record<string, string>;
|