@davidvornholt/standards 0.26.0 → 0.26.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/README.md +2 -2
- package/package.json +4 -5
- package/src/cli.ts +1 -1
- package/src/creds-cloudflare.ts +2 -0
- package/src/creds-github-apps.ts +2 -0
- package/src/creds-plan.ts +2 -0
- package/src/creds-store.ts +3 -0
- package/src/dev-env-brokered-resolve.ts +2 -0
- package/src/github-graphql.ts +2 -0
- package/src/poller-codex.ts +3 -0
- package/src/structure-check.ts +10 -12
- package/src/structure-readme.ts +0 -85
package/README.md
CHANGED
|
@@ -60,7 +60,7 @@ Existing project-owned files are kept. Synced files are replaced. `init` refuses
|
|
|
60
60
|
```sh
|
|
61
61
|
bun standards sync --dry-run
|
|
62
62
|
bun standards sync
|
|
63
|
-
bun standards sync --ref v0.26.
|
|
63
|
+
bun standards sync --ref v0.26.1
|
|
64
64
|
bun standards sync --from ../standards
|
|
65
65
|
```
|
|
66
66
|
|
|
@@ -97,7 +97,7 @@ bun standards structure --profile source
|
|
|
97
97
|
|
|
98
98
|
`doctor` checks project-owned integration points such as `biome.jsonc`, `AGENTS.local.md`, package scripts, sync policy, settings overlays, and Dependabot inputs.
|
|
99
99
|
|
|
100
|
-
`structure` checks the exact Bun declaration, workspace scripts, internal dependency versions, package exports, TypeScript inheritance, accessibility wiring,
|
|
100
|
+
`structure` checks the exact Bun declaration, workspace scripts, internal dependency versions, package exports, TypeScript inheritance, accessibility wiring, and CI secret structure. The `source` profile records the few intentional differences in this repository itself.
|
|
101
101
|
|
|
102
102
|
## dependabot
|
|
103
103
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@davidvornholt/standards",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.2",
|
|
4
4
|
"description": "Bootstrap, synchronize, and validate davidvornholt/standards consumers",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -166,7 +166,6 @@
|
|
|
166
166
|
"src/structure-check.ts",
|
|
167
167
|
"src/structure-bun-version.ts",
|
|
168
168
|
"src/structure-profile.ts",
|
|
169
|
-
"src/structure-readme.ts",
|
|
170
169
|
"src/structure-secret-document.ts",
|
|
171
170
|
"src/structure-script.ts",
|
|
172
171
|
"src/structure-secrets.ts",
|
|
@@ -184,17 +183,17 @@
|
|
|
184
183
|
},
|
|
185
184
|
"devDependencies": {
|
|
186
185
|
"@davidvornholt/typescript-config": "0.0.0",
|
|
187
|
-
"@types/bun": "^1.4.
|
|
186
|
+
"@types/bun": "^1.4.2",
|
|
188
187
|
"sigstore": "5.0.0",
|
|
189
188
|
"typescript": "7.0.2"
|
|
190
189
|
},
|
|
191
190
|
"engines": {
|
|
192
|
-
"bun": ">=1.4.
|
|
191
|
+
"bun": ">=1.4.2"
|
|
193
192
|
},
|
|
194
193
|
"publishConfig": {
|
|
195
194
|
"access": "public"
|
|
196
195
|
},
|
|
197
196
|
"dependencies": {
|
|
198
|
-
"yaml": "^2.9.
|
|
197
|
+
"yaml": "^2.9.1"
|
|
199
198
|
}
|
|
200
199
|
}
|
package/src/cli.ts
CHANGED
|
@@ -1189,7 +1189,7 @@ const parseArgs = (argv: ReadonlyArray<string>): CliOptions => {
|
|
|
1189
1189
|
|
|
1190
1190
|
// Canonical monorepo structure gate: workspace and root script shapes,
|
|
1191
1191
|
// internal versioning, `exports`, tsconfig inheritance, a11y wiring,
|
|
1192
|
-
//
|
|
1192
|
+
// and the undecrypted CI secrets contract
|
|
1193
1193
|
// (SOPS shape mirroring plus the keys the synced workflows require).
|
|
1194
1194
|
// The `source` profile instead pins the standards template repository's own
|
|
1195
1195
|
// deliberate exceptions to the consumer contract.
|
package/src/creds-cloudflare.ts
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
// itself, lists and mints scoped tokens, and revokes them. Token values
|
|
4
4
|
// returned by create flow only into SOPS writers,
|
|
5
5
|
// never stdout.
|
|
6
|
+
// User-owned tokens are intentionally outside this API and the bootstrap
|
|
7
|
+
// credential's authority; they must be managed separately in Cloudflare.
|
|
6
8
|
|
|
7
9
|
import {
|
|
8
10
|
type CfResult,
|
package/src/creds-github-apps.ts
CHANGED
|
@@ -57,6 +57,8 @@ export const loadOwnedGithubStore = async (
|
|
|
57
57
|
return { ok: true, value: migrated };
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
+
// Private Apps belong to one owner. Selecting by repository owner keeps each
|
|
61
|
+
// owner's credentials separate; installation access is verified before export.
|
|
60
62
|
export const selectGithubAppForRepo = (
|
|
61
63
|
apps: ReadonlyArray<GithubBrokerApp>,
|
|
62
64
|
repo: string,
|
package/src/creds-plan.ts
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
// tokens it minted for another repository, are reported
|
|
8
8
|
// (creds-plan-unmanaged.ts) but never mutated. Execution lives in
|
|
9
9
|
// creds-plan-run.ts.
|
|
10
|
+
// Live policy is preserved, not compared with a separate desired-policy manifest.
|
|
11
|
+
// Adding policy-drift enforcement would require a new source of truth.
|
|
10
12
|
|
|
11
13
|
import { cloudflareExpiresOn } from './creds-cloudflare-expiry';
|
|
12
14
|
import { groupByIntersectingFootprint } from './creds-plan-groups';
|
package/src/creds-store.ts
CHANGED
|
@@ -24,6 +24,9 @@ export type BrokerStore = {
|
|
|
24
24
|
readonly cloudflare: ReadonlyArray<CloudflareBrokerAccount>;
|
|
25
25
|
};
|
|
26
26
|
export const EMPTY_BROKER_STORE: BrokerStore = { github: [], cloudflare: [] };
|
|
27
|
+
// This machine-global store trusts the local account, like its age private key.
|
|
28
|
+
// Encrypting it with a colocated key would not protect against that account.
|
|
29
|
+
// Repository secrets still belong in SOPS; this store stays outside repositories.
|
|
27
30
|
const OWNER_ONLY_FILE_MODE = 0o600;
|
|
28
31
|
const OWNER_ONLY_DIR_MODE = 0o700;
|
|
29
32
|
const FILE_MODE_MODULUS = 0o1000;
|
|
@@ -22,6 +22,8 @@ export type ResolvedDevEnv = {
|
|
|
22
22
|
readonly problems: ReadonlyArray<string>;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
+
// Each variable independently selects a complete pair and then one part.
|
|
26
|
+
// Variable names and neighboring references do not imply a shared credential.
|
|
25
27
|
const lookupReference = (
|
|
26
28
|
document: unknown,
|
|
27
29
|
reference: BrokeredS3Reference,
|
package/src/github-graphql.ts
CHANGED
|
@@ -120,6 +120,8 @@ const BYPASS_ACTOR_PROBE = 1;
|
|
|
120
120
|
// `totalCount` means the connection is being filtered rather than merely
|
|
121
121
|
// redacted, so it supplies no count; a null *element* is the documented
|
|
122
122
|
// identity-withholding shape, so only the length is checked.
|
|
123
|
+
// Probes with restricted tokens confirmed that hidden actors still count:
|
|
124
|
+
// https://github.com/davidvornholt/standards/pull/176
|
|
123
125
|
const countedRuleset = (node: unknown): readonly [number, number] | null => {
|
|
124
126
|
if (!isRecord(node) || typeof node.databaseId !== 'number') {
|
|
125
127
|
return null;
|
package/src/poller-codex.ts
CHANGED
|
@@ -163,6 +163,9 @@ export const runCodex = async (
|
|
|
163
163
|
{
|
|
164
164
|
detached: true,
|
|
165
165
|
stdio: ['ignore', 'ignore', 'pipe'],
|
|
166
|
+
// Approved runs share the host service identity and its GitHub token.
|
|
167
|
+
// Approval and write revalidation constrain the workflow; they do not
|
|
168
|
+
// isolate credentials. This assumes trusted, maintainer-approved work.
|
|
166
169
|
env: { ...process.env },
|
|
167
170
|
},
|
|
168
171
|
);
|
package/src/structure-check.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { readdir } from 'node:fs/promises';
|
|
3
3
|
import { isAbsolute, join } from 'node:path';
|
|
4
|
+
import { isContainedPath } from './contained-path';
|
|
4
5
|
import { isRecord } from './github-settings-parse';
|
|
5
6
|
import { readJsonFile } from './json-file';
|
|
6
7
|
import { collectBunVersionProblems } from './structure-bun-version';
|
|
@@ -9,7 +10,6 @@ import {
|
|
|
9
10
|
rootScriptExpectations,
|
|
10
11
|
type StructureProfile,
|
|
11
12
|
} from './structure-profile';
|
|
12
|
-
import { collectWorkspaceReadmeProblems } from './structure-readme';
|
|
13
13
|
import { filteredTurboAliasProblem, hasSafeCommands } from './structure-script';
|
|
14
14
|
import { collectCiSecretsProblems } from './structure-secrets';
|
|
15
15
|
import { inspectWorkspace, type Workspace } from './structure-workspace';
|
|
@@ -106,6 +106,12 @@ const loadWorkspace = async (
|
|
|
106
106
|
if (!existsSync(path)) {
|
|
107
107
|
return { workspace: null, problem: null };
|
|
108
108
|
}
|
|
109
|
+
if (!isContainedPath(consumer, `${rel}/package.json`, 'file')) {
|
|
110
|
+
return {
|
|
111
|
+
workspace: null,
|
|
112
|
+
problem: `${rel}: package.json must be a contained regular file; symlinked paths are not allowed`,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
109
115
|
const manifest = await readJsonFile(path);
|
|
110
116
|
if (manifest === null) {
|
|
111
117
|
return {
|
|
@@ -174,16 +180,9 @@ export const collectStructureProblems = async (
|
|
|
174
180
|
.map((ws) => ws.manifest.name)
|
|
175
181
|
.filter((name): name is string => typeof name === 'string'),
|
|
176
182
|
);
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
),
|
|
181
|
-
collectWorkspaceReadmeProblems(
|
|
182
|
-
consumer,
|
|
183
|
-
profile,
|
|
184
|
-
workspaces.map((ws) => ws.rel),
|
|
185
|
-
),
|
|
186
|
-
]);
|
|
183
|
+
const inspections = await Promise.all(
|
|
184
|
+
workspaces.map((ws) => inspectWorkspace(ws, workspaceNames, profile)),
|
|
185
|
+
);
|
|
187
186
|
const requireA11y = inspections.some((i) => i.hasA11ySuite);
|
|
188
187
|
return [
|
|
189
188
|
...declaration.problems,
|
|
@@ -193,7 +192,6 @@ export const collectStructureProblems = async (
|
|
|
193
192
|
...collectBunVersionProblems(root),
|
|
194
193
|
...inspectRootScripts(root, profile, requireA11y),
|
|
195
194
|
...inspections.flatMap((i) => [...i.problems]),
|
|
196
|
-
...readmeProblems,
|
|
197
195
|
...secretsProblems,
|
|
198
196
|
];
|
|
199
197
|
};
|
package/src/structure-readme.ts
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { lstat, readFile } from 'node:fs/promises';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { isContainedPath } from './contained-path';
|
|
4
|
-
import { readJsonFile } from './json-file';
|
|
5
|
-
import type { StructureProfile } from './structure-profile';
|
|
6
|
-
|
|
7
|
-
const SYNC_MANIFEST = 'sync-standards.json';
|
|
8
|
-
const TRAILING_SLASHES = /\/+$/u;
|
|
9
|
-
const MANIFEST_REQUIREMENT = `${SYNC_MANIFEST}: must contain a JSON object with a "paths" array of strings; the structure gate reads it to tell canonical workspaces from repo-owned ones`;
|
|
10
|
-
const MANIFEST_CONTAINMENT = `${SYNC_MANIFEST}: must be a contained regular file; symlinked paths are not allowed`;
|
|
11
|
-
|
|
12
|
-
type CanonicalRels = {
|
|
13
|
-
readonly rels: ReadonlySet<string>;
|
|
14
|
-
readonly problem: string | null;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
// Canonical workspaces are synced from the standards template and cannot be
|
|
18
|
-
// fixed in a consumer checkout, so the consumer profile exempts them from
|
|
19
|
-
// repo-owned documentation rules. The source repository owns those workspaces,
|
|
20
|
-
// so the source profile exempts nothing.
|
|
21
|
-
const canonicalWorkspaceRels = async (
|
|
22
|
-
consumer: string,
|
|
23
|
-
profile: StructureProfile,
|
|
24
|
-
): Promise<CanonicalRels> => {
|
|
25
|
-
if (profile === 'source') {
|
|
26
|
-
return { rels: new Set(), problem: null };
|
|
27
|
-
}
|
|
28
|
-
const manifestPath = join(consumer, SYNC_MANIFEST);
|
|
29
|
-
const manifestInfo = await lstat(manifestPath).catch(() => null);
|
|
30
|
-
if (
|
|
31
|
-
manifestInfo !== null &&
|
|
32
|
-
!isContainedPath(consumer, SYNC_MANIFEST, 'file')
|
|
33
|
-
) {
|
|
34
|
-
return { rels: new Set(), problem: MANIFEST_CONTAINMENT };
|
|
35
|
-
}
|
|
36
|
-
const manifest = await readJsonFile(manifestPath);
|
|
37
|
-
const paths = manifest?.paths;
|
|
38
|
-
if (
|
|
39
|
-
!(
|
|
40
|
-
Array.isArray(paths) &&
|
|
41
|
-
paths.every((path): path is string => typeof path === 'string')
|
|
42
|
-
)
|
|
43
|
-
) {
|
|
44
|
-
return { rels: new Set(), problem: MANIFEST_REQUIREMENT };
|
|
45
|
-
}
|
|
46
|
-
return {
|
|
47
|
-
rels: new Set(paths.map((path) => path.replace(TRAILING_SLASHES, ''))),
|
|
48
|
-
problem: null,
|
|
49
|
-
};
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const readmeProblem = async (
|
|
53
|
-
consumer: string,
|
|
54
|
-
rel: string,
|
|
55
|
-
): Promise<string | null> => {
|
|
56
|
-
const readmeRel = `${rel}/README.md`;
|
|
57
|
-
const readmePath = join(consumer, readmeRel);
|
|
58
|
-
const info = await lstat(readmePath).catch(() => null);
|
|
59
|
-
if (info !== null && !isContainedPath(consumer, readmeRel, 'file')) {
|
|
60
|
-
return `${rel}: README.md must be a contained regular file; symlinked paths are not allowed`;
|
|
61
|
-
}
|
|
62
|
-
const raw = await readFile(readmePath, 'utf8').catch(() => null);
|
|
63
|
-
return raw !== null && raw.trim() !== ''
|
|
64
|
-
? null
|
|
65
|
-
: `${rel}: repo-owned workspace must have a non-empty README.md`;
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
export const collectWorkspaceReadmeProblems = async (
|
|
69
|
-
consumer: string,
|
|
70
|
-
profile: StructureProfile,
|
|
71
|
-
rels: ReadonlyArray<string>,
|
|
72
|
-
): Promise<ReadonlyArray<string>> => {
|
|
73
|
-
const canonical = await canonicalWorkspaceRels(consumer, profile);
|
|
74
|
-
if (canonical.problem !== null) {
|
|
75
|
-
// Without the manifest the gate cannot tell canonical workspaces from
|
|
76
|
-
// repo-owned ones, so it fails on the manifest instead of guessing.
|
|
77
|
-
return [canonical.problem];
|
|
78
|
-
}
|
|
79
|
-
const problems = await Promise.all(
|
|
80
|
-
rels
|
|
81
|
-
.filter((rel) => !canonical.rels.has(rel))
|
|
82
|
-
.map((rel) => readmeProblem(consumer, rel)),
|
|
83
|
-
);
|
|
84
|
-
return problems.filter((problem): problem is string => problem !== null);
|
|
85
|
-
};
|