@ankhorage/devtools 1.14.8 → 1.15.1
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 +114 -1
- package/dist/apmRelease.d.ts +4 -0
- package/dist/apmRelease.js +3 -0
- package/dist/cli/bin/apm-release.d.ts +2 -0
- package/dist/cli/bin/apm-release.js +15 -0
- package/dist/cli/commands/apm/sync.d.ts +5 -0
- package/dist/cli/commands/apm/sync.js +5 -0
- package/dist/cli/commands/apm/validate.d.ts +5 -0
- package/dist/cli/commands/apm/validate.js +5 -0
- package/dist/cli/commands.d.ts +6 -2
- package/dist/cli/commands.js +15 -1
- package/dist/cli/index.d.ts +2 -2
- package/dist/cli/index.js +9 -0
- package/dist/cli/runProviderCommand.js +5 -0
- package/dist/cli/runRepositoryCommand.js +2 -1
- package/dist/features/apm-release-validation/adapters/inbound/runApmReleaseCommandAsync.d.ts +10 -0
- package/dist/features/apm-release-validation/adapters/inbound/runApmReleaseCommandAsync.js +51 -0
- package/dist/features/apm-release-validation/adapters/outbound/createApmReleaseDescriptorPort.d.ts +3 -0
- package/dist/features/apm-release-validation/adapters/outbound/createApmReleaseDescriptorPort.js +31 -0
- package/dist/features/apm-release-validation/adapters/outbound/inspectPackedApmReleaseCandidateAsync.d.ts +3 -0
- package/dist/features/apm-release-validation/adapters/outbound/inspectPackedApmReleaseCandidateAsync.js +84 -0
- package/dist/features/apm-release-validation/adapters/outbound/probePackedApmExtensionAsync.d.ts +11 -0
- package/dist/features/apm-release-validation/adapters/outbound/probePackedApmExtensionAsync.js +82 -0
- package/dist/features/apm-release-validation/adapters/outbound/readApmReleaseManifestAsync.d.ts +2 -0
- package/dist/features/apm-release-validation/adapters/outbound/readApmReleaseManifestAsync.js +9 -0
- package/dist/features/apm-release-validation/adapters/outbound/resolveApmReleaseCommandAsync.d.ts +2 -0
- package/dist/features/apm-release-validation/adapters/outbound/resolveApmReleaseCommandAsync.js +18 -0
- package/dist/features/apm-release-validation/application/synchronizeApmReleaseMetadataAsync.d.ts +3 -0
- package/dist/features/apm-release-validation/application/synchronizeApmReleaseMetadataAsync.js +29 -0
- package/dist/features/apm-release-validation/application/validateApmReleaseCandidate.d.ts +11 -0
- package/dist/features/apm-release-validation/application/validateApmReleaseCandidate.js +76 -0
- package/dist/features/apm-release-validation/composition/synchronizeApmReleaseDescriptorAsync.d.ts +2 -0
- package/dist/features/apm-release-validation/composition/synchronizeApmReleaseDescriptorAsync.js +6 -0
- package/dist/features/apm-release-validation/composition/validatePackedApmReleaseAsync.d.ts +12 -0
- package/dist/features/apm-release-validation/composition/validatePackedApmReleaseAsync.js +14 -0
- package/dist/features/apm-release-validation/utils/readApmReleaseMetadata.d.ts +2 -0
- package/dist/features/apm-release-validation/utils/readApmReleaseMetadata.js +6 -0
- package/dist/tools/agents/index.js +58 -18
- package/dist/tools/gitignore/index.d.ts +10 -0
- package/dist/tools/gitignore/index.js +43 -0
- package/dist/tools/skills/assets/ankhorage-coding-rules/SKILL.md +28 -11
- package/dist/tools/workflows/files/ci.yml +7 -0
- package/dist/tools/workflows/files/release.yml +35 -1
- package/dist/tools/workflows/index.js +4 -1
- package/dist/tools/workflows/renderWorkflowAsync.d.ts +1 -0
- package/dist/tools/workflows/renderWorkflowAsync.js +1 -0
- package/dist/types/apm-release-validation.d.ts +33 -0
- package/dist/types/apm-release-validation.js +1 -0
- package/dist/types/public.d.ts +1 -0
- package/dist/types/public.js +1 -0
- package/package.json +17 -4
package/dist/features/apm-release-validation/adapters/outbound/resolveApmReleaseCommandAsync.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { readApmReleaseManifestAsync } from './readApmReleaseManifestAsync.js';
|
|
3
|
+
/*** Use Devtools' own built CLI for self-validation and the installed dependency binary in consumers. */
|
|
4
|
+
export async function resolveApmReleaseCommandAsync(targetDirectory) {
|
|
5
|
+
try {
|
|
6
|
+
const manifest = await readApmReleaseManifestAsync(resolve(targetDirectory, 'package.json'));
|
|
7
|
+
return manifest.name === '@ankhorage/devtools'
|
|
8
|
+
? 'node ./dist/cli/bin/apm-release.js'
|
|
9
|
+
: CONSUMER_COMMAND;
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
13
|
+
return CONSUMER_COMMAND;
|
|
14
|
+
}
|
|
15
|
+
throw error;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const CONSUMER_COMMAND = './node_modules/.bin/ankhorage-apm-release';
|
package/dist/features/apm-release-validation/application/synchronizeApmReleaseMetadataAsync.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ApmReleaseDescriptorPort } from '../../../types/apm-release-validation.js';
|
|
2
|
+
/*** Bind valid owner metadata to the selected candidate version; never silently skip malformed opt-in. */
|
|
3
|
+
export declare function synchronizeApmReleaseMetadataAsync(port: ApmReleaseDescriptorPort): Promise<boolean>;
|
package/dist/features/apm-release-validation/application/synchronizeApmReleaseMetadataAsync.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { validatePackageUpdateMetadata, validateUpdateDescriptor } from '@ankhorage/apm';
|
|
2
|
+
import { isRecord } from '@ankhorage/utility/object';
|
|
3
|
+
import { readApmReleaseMetadata } from '../utils/readApmReleaseMetadata.js';
|
|
4
|
+
/*** Bind valid owner metadata to the selected candidate version; never silently skip malformed opt-in. */
|
|
5
|
+
export async function synchronizeApmReleaseMetadataAsync(port) {
|
|
6
|
+
const manifest = await port.readPackageAsync();
|
|
7
|
+
const metadata = readApmReleaseMetadata(manifest);
|
|
8
|
+
if (metadata === undefined)
|
|
9
|
+
return false;
|
|
10
|
+
const discovery = validatePackageUpdateMetadata(metadata);
|
|
11
|
+
if (!discovery.valid || discovery.metadata === undefined)
|
|
12
|
+
throw new Error('Invalid APM discovery metadata.');
|
|
13
|
+
const descriptor = await port.readDescriptorAsync(discovery.metadata.descriptor);
|
|
14
|
+
const { name, version } = manifest;
|
|
15
|
+
if (typeof name !== 'string' ||
|
|
16
|
+
typeof version !== 'string' ||
|
|
17
|
+
!isRecord(descriptor.owner) ||
|
|
18
|
+
descriptor.owner.name !== name) {
|
|
19
|
+
throw new Error('APM release descriptor must belong to the package being versioned.');
|
|
20
|
+
}
|
|
21
|
+
const synchronized = { ...descriptor, owner: { ...descriptor.owner, version } };
|
|
22
|
+
const validation = validateUpdateDescriptor({
|
|
23
|
+
descriptor: synchronized,
|
|
24
|
+
expectedOwner: { name, version },
|
|
25
|
+
});
|
|
26
|
+
if (!validation.valid)
|
|
27
|
+
throw new Error(validation.blockers.map(({ code, reason }) => `${code}: ${reason}`).join('\n'));
|
|
28
|
+
return port.writeDescriptorAsync(discovery.metadata.descriptor, synchronized);
|
|
29
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ApmPackedReleaseCandidate, ApmReleaseValidationOptions, ApmReleaseValidationResult } from '../../../types/apm-release-validation.js';
|
|
2
|
+
/*** Validate packed owner evidence using the released APM protocol, never a copied schema.
|
|
3
|
+
* Opt-in packages declare ankh.apm with protocolVersion and a package-relative descriptor.
|
|
4
|
+
* No metadata means not applicable, not a claim that no migration is needed. The descriptor
|
|
5
|
+
* declares supported no-migration, required migration, or unsupported/manual history explicitly.
|
|
6
|
+
* Previous descriptors enforce immutable migration checksums and related descriptors validate
|
|
7
|
+
* cross-owner prerequisites. Package-owned source-to-target tests must also cover skipped versions,
|
|
8
|
+
* idempotency, interruption/recovery, and unsupported historical states.
|
|
9
|
+
* @readme
|
|
10
|
+
*/
|
|
11
|
+
export declare function validateApmReleaseCandidate(candidate: ApmPackedReleaseCandidate, options?: ApmReleaseValidationOptions): ApmReleaseValidationResult;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { validatePackageUpdateMetadata, validateUpdateDescriptor, validateUpdateExtensionBinding, validateUpdateExtensionCapabilities, } from '@ankhorage/apm';
|
|
3
|
+
import { readApmReleaseMetadata } from '../utils/readApmReleaseMetadata.js';
|
|
4
|
+
/*** Validate packed owner evidence using the released APM protocol, never a copied schema.
|
|
5
|
+
* Opt-in packages declare ankh.apm with protocolVersion and a package-relative descriptor.
|
|
6
|
+
* No metadata means not applicable, not a claim that no migration is needed. The descriptor
|
|
7
|
+
* declares supported no-migration, required migration, or unsupported/manual history explicitly.
|
|
8
|
+
* Previous descriptors enforce immutable migration checksums and related descriptors validate
|
|
9
|
+
* cross-owner prerequisites. Package-owned source-to-target tests must also cover skipped versions,
|
|
10
|
+
* idempotency, interruption/recovery, and unsupported historical states.
|
|
11
|
+
* @readme
|
|
12
|
+
*/
|
|
13
|
+
export function validateApmReleaseCandidate(candidate, options = {}) {
|
|
14
|
+
const { name, version } = candidate.packageJson;
|
|
15
|
+
const identity = {
|
|
16
|
+
...(typeof name === 'string' ? { packageName: name } : {}),
|
|
17
|
+
...(typeof version === 'string' ? { packageVersion: version } : {}),
|
|
18
|
+
integrity: candidate.integrity,
|
|
19
|
+
};
|
|
20
|
+
const metadata = readApmReleaseMetadata(candidate.packageJson);
|
|
21
|
+
const integrityBlockers = options.expectedIntegrity !== undefined && options.expectedIntegrity !== candidate.integrity
|
|
22
|
+
? ['release.integrity-mismatch: Packed bytes differ from the selected release artifact.']
|
|
23
|
+
: [];
|
|
24
|
+
if (metadata === undefined) {
|
|
25
|
+
return {
|
|
26
|
+
...identity,
|
|
27
|
+
applicable: false,
|
|
28
|
+
valid: integrityBlockers.length === 0,
|
|
29
|
+
blockers: integrityBlockers,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const metadataValidation = validatePackageUpdateMetadata(metadata);
|
|
33
|
+
if (typeof name !== 'string' || !name || typeof version !== 'string' || !version) {
|
|
34
|
+
return result(identity, ['release.identity: Package must define name and exact version.']);
|
|
35
|
+
}
|
|
36
|
+
if (!metadataValidation.valid)
|
|
37
|
+
return result(identity, [...integrityBlockers, ...messages(metadataValidation.blockers)]);
|
|
38
|
+
const validation = validateUpdateDescriptor({
|
|
39
|
+
descriptor: candidate.descriptor,
|
|
40
|
+
expectedOwner: { name, version },
|
|
41
|
+
previousDescriptors: options.previousDescriptors,
|
|
42
|
+
relatedDescriptors: options.relatedDescriptors,
|
|
43
|
+
});
|
|
44
|
+
if (!validation.valid || validation.descriptor === undefined || integrityBlockers.length > 0) {
|
|
45
|
+
return result(identity, [...integrityBlockers, ...messages(validation.blockers)]);
|
|
46
|
+
}
|
|
47
|
+
return result(identity, extensionBlockers(candidate, validation.descriptor));
|
|
48
|
+
}
|
|
49
|
+
/*** Validate executable binding and required handlers after static metadata has passed. */
|
|
50
|
+
function extensionBlockers(candidate, descriptor) {
|
|
51
|
+
if (descriptor.extension === undefined)
|
|
52
|
+
return [];
|
|
53
|
+
const artifact = {
|
|
54
|
+
role: 'target',
|
|
55
|
+
packageName: descriptor.owner.name,
|
|
56
|
+
version: descriptor.owner.version,
|
|
57
|
+
integrity: candidate.integrity,
|
|
58
|
+
descriptorDigest: createHash('sha256').update(candidate.descriptorSource).digest('hex'),
|
|
59
|
+
};
|
|
60
|
+
if (!isBoundExtension(artifact, candidate.extension)) {
|
|
61
|
+
return messages(validateUpdateExtensionBinding(artifact, candidate.extension));
|
|
62
|
+
}
|
|
63
|
+
return messages(validateUpdateExtensionCapabilities(descriptor, artifact, candidate.extension));
|
|
64
|
+
}
|
|
65
|
+
/*** Narrow unknown executable evidence only after the canonical runtime validator accepts it. */
|
|
66
|
+
function isBoundExtension(artifact, value) {
|
|
67
|
+
return validateUpdateExtensionBinding(artifact, value).length === 0;
|
|
68
|
+
}
|
|
69
|
+
/*** Preserve structured candidate identity and canonical blocker reasons. */
|
|
70
|
+
function result(identity, blockers) {
|
|
71
|
+
return { ...identity, applicable: true, valid: blockers.length === 0, blockers };
|
|
72
|
+
}
|
|
73
|
+
/*** Render the protocol's own diagnostics without reinterpreting their semantics. */
|
|
74
|
+
function messages(blockers) {
|
|
75
|
+
return blockers.map(({ code, reason }) => `${code}: ${reason}`);
|
|
76
|
+
}
|
package/dist/features/apm-release-validation/composition/synchronizeApmReleaseDescriptorAsync.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createApmReleaseDescriptorPort } from '../adapters/outbound/createApmReleaseDescriptorPort.js';
|
|
2
|
+
import { synchronizeApmReleaseMetadataAsync } from '../application/synchronizeApmReleaseMetadataAsync.js';
|
|
3
|
+
/*** Compose descriptor version synchronization with the bounded Node filesystem adapter. */
|
|
4
|
+
export async function synchronizeApmReleaseDescriptorAsync(targetDirectory) {
|
|
5
|
+
return synchronizeApmReleaseMetadataAsync(createApmReleaseDescriptorPort(targetDirectory));
|
|
6
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ApmReleaseValidationOptions, ApmReleaseValidationResult } from '../../../types/apm-release-validation.js';
|
|
2
|
+
/*** Validate script-free publish bytes and optionally retain that exact accepted archive.
|
|
3
|
+
* Release order is APM protocol, package-owner metadata/code, then consuming applications.
|
|
4
|
+
* Build after Changesets versioning and descriptor synchronization. Execute package-owned
|
|
5
|
+
* acceptance tests, then validate with explicit owner-code consent. The child probe uses the
|
|
6
|
+
* packed package's native public export resolution and installed dependencies, not omitted source.
|
|
7
|
+
* Consent permits trusted executable code; a child process is not a security sandbox.
|
|
8
|
+
* An artifactPath retains accepted bytes exclusively, so publication need not repack or rerun
|
|
9
|
+
* lifecycle scripts. expectedIntegrity rejects a stale candidate before any owner code loads.
|
|
10
|
+
* @readme
|
|
11
|
+
*/
|
|
12
|
+
export declare function validatePackedApmReleaseAsync(targetDirectory: string, options?: ApmReleaseValidationOptions): Promise<ApmReleaseValidationResult>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { inspectPackedApmReleaseCandidateAsync } from '../adapters/outbound/inspectPackedApmReleaseCandidateAsync.js';
|
|
2
|
+
/*** Validate script-free publish bytes and optionally retain that exact accepted archive.
|
|
3
|
+
* Release order is APM protocol, package-owner metadata/code, then consuming applications.
|
|
4
|
+
* Build after Changesets versioning and descriptor synchronization. Execute package-owned
|
|
5
|
+
* acceptance tests, then validate with explicit owner-code consent. The child probe uses the
|
|
6
|
+
* packed package's native public export resolution and installed dependencies, not omitted source.
|
|
7
|
+
* Consent permits trusted executable code; a child process is not a security sandbox.
|
|
8
|
+
* An artifactPath retains accepted bytes exclusively, so publication need not repack or rerun
|
|
9
|
+
* lifecycle scripts. expectedIntegrity rejects a stale candidate before any owner code loads.
|
|
10
|
+
* @readme
|
|
11
|
+
*/
|
|
12
|
+
export async function validatePackedApmReleaseAsync(targetDirectory, options = {}) {
|
|
13
|
+
return inspectPackedApmReleaseCandidateAsync(targetDirectory, options);
|
|
14
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { isRecord, readOwnProperty } from '@ankhorage/utility/object';
|
|
2
|
+
/*** Read opt-in owner metadata without copying the APM discovery schema. */
|
|
3
|
+
export function readApmReleaseMetadata(manifest) {
|
|
4
|
+
const ankh = readOwnProperty(manifest, 'ankh');
|
|
5
|
+
return isRecord(ankh) ? readOwnProperty(ankh, 'apm') : undefined;
|
|
6
|
+
}
|
|
@@ -11,6 +11,13 @@ async function renderAgentsFile(targetDirectory) {
|
|
|
11
11
|
const manifest = await readPackageManifest(targetDirectory);
|
|
12
12
|
const packageName = readNonEmptyString(manifest.name) ?? 'Package name not declared';
|
|
13
13
|
const description = readNonEmptyString(manifest.description) ?? 'No package description is declared.';
|
|
14
|
+
const sections = [
|
|
15
|
+
renderCurrentArchitectureInstructions(),
|
|
16
|
+
renderRequiredRepositoryInstructions(),
|
|
17
|
+
renderDocumentationInstructions(),
|
|
18
|
+
renderPullRequestInstructions(),
|
|
19
|
+
renderSkillScriptInstructions(),
|
|
20
|
+
].join('');
|
|
14
21
|
return `# AGENTS.md
|
|
15
22
|
|
|
16
23
|
<!-- This file is managed by @ankhorage/devtools. -->
|
|
@@ -21,7 +28,11 @@ Package: \`${escapeInlineCode(packageName)}\`
|
|
|
21
28
|
|
|
22
29
|
${description}
|
|
23
30
|
|
|
24
|
-
|
|
31
|
+
${sections}`;
|
|
32
|
+
}
|
|
33
|
+
/*** Render the current-architecture policy shared by every managed repository. */
|
|
34
|
+
function renderCurrentArchitectureInstructions() {
|
|
35
|
+
return `## Current architecture only
|
|
25
36
|
|
|
26
37
|
Only the current Ankhorage architecture is valid. Do not add or retain deprecated APIs,
|
|
27
38
|
compatibility aliases, shims, dual old/new paths, historical-state fallbacks, or migrations whose
|
|
@@ -34,27 +45,47 @@ published public APIs and declared dependencies, never sibling source files.
|
|
|
34
45
|
Current-runtime error handling and canonical database or infrastructure migrations remain valid
|
|
35
46
|
when they support states that the current architecture can intentionally produce.
|
|
36
47
|
|
|
37
|
-
|
|
48
|
+
`;
|
|
49
|
+
}
|
|
50
|
+
/*** Render contextual repository skill-selection requirements. */
|
|
51
|
+
function renderRequiredRepositoryInstructions() {
|
|
52
|
+
return `## Required repository instructions
|
|
38
53
|
|
|
39
54
|
Before changing any file, read this \`AGENTS.md\` completely and inspect \`.agents/skills/\`.
|
|
40
|
-
|
|
55
|
+
Treat skill selection as a mandatory precondition to editing, then follow every selected skill
|
|
56
|
+
through validation and delivery.
|
|
57
|
+
|
|
58
|
+
- Load \`.agents/skills/ankhorage-coding-rules/SKILL.md\` for implementation, refactoring, testing,
|
|
59
|
+
review, or pull-request delivery work.
|
|
60
|
+
- Load \`.agents/skills/ankhorage-project-structure/SKILL.md\` when the task changes or reviews
|
|
61
|
+
directory ownership, package boundaries, public entrypoints, cross-repository ownership, type or
|
|
62
|
+
utility placement, or source architecture.
|
|
63
|
+
- Load every additional repository-local skill whose description or requirements match the task,
|
|
64
|
+
including skills required by any selected skill. These rules define mandatory minimums, not an
|
|
65
|
+
allow-list; do not skip a useful relevant skill merely because it is not named here.
|
|
66
|
+
|
|
67
|
+
Do not load unrelated skills merely because they are installed. If the task scope expands, inspect
|
|
68
|
+
\`.agents/skills/\` again and load the newly relevant skills before continuing. Do not substitute
|
|
69
|
+
remembered, globally installed, or generic guidance for the repository-local versions.
|
|
41
70
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
validation and delivery; do not substitute remembered, globally installed, or generic guidance for
|
|
48
|
-
the repository-local versions.
|
|
49
|
-
|
|
50
|
-
## Documentation
|
|
71
|
+
`;
|
|
72
|
+
}
|
|
73
|
+
/*** Render the release-owned generated documentation policy. */
|
|
74
|
+
function renderDocumentationInstructions() {
|
|
75
|
+
return `## Documentation
|
|
51
76
|
|
|
52
|
-
\`README.md\` and the
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
77
|
+
\`README.md\` and the configured Paradox output are generated release artifacts. Never edit them
|
|
78
|
+
manually, and do not regenerate or commit them in ordinary feature pull requests. Update the owning
|
|
79
|
+
Paradox \`/*** ... */\` comments in \`src\` and leading \`@usage\` comments in real
|
|
80
|
+
\`examples/<example>/...\` source files, plus any repository-owned manual documentation outside the
|
|
81
|
+
generated output. The managed release workflow runs \`bun run docs\` after the package version bump
|
|
82
|
+
and commits the regenerated artifacts in the release commit.
|
|
56
83
|
|
|
57
|
-
|
|
84
|
+
`;
|
|
85
|
+
}
|
|
86
|
+
/*** Render canonical pull-request validation commands. */
|
|
87
|
+
function renderPullRequestInstructions() {
|
|
88
|
+
return `## Pull requests
|
|
58
89
|
|
|
59
90
|
Before creating a pull request, run all of these commands in this order and resolve every failure:
|
|
60
91
|
|
|
@@ -67,13 +98,18 @@ bun run changeset
|
|
|
67
98
|
bun run format
|
|
68
99
|
\`\`\`
|
|
69
100
|
|
|
70
|
-
|
|
101
|
+
`;
|
|
102
|
+
}
|
|
103
|
+
/*** Render the repository rule for executable Agent Skill scripts. */
|
|
104
|
+
function renderSkillScriptInstructions() {
|
|
105
|
+
return `## Skill scripts
|
|
71
106
|
|
|
72
107
|
Scripts inside an Agent Skill must always be TypeScript files with the \`.ts\` extension.
|
|
73
108
|
JavaScript skill scripts using \`.js\`, \`.mjs\`, or \`.cjs\` are not allowed. Run TypeScript
|
|
74
109
|
skill scripts with Bun.
|
|
75
110
|
`;
|
|
76
111
|
}
|
|
112
|
+
/*** Read the target package manifest used to personalize managed repository instructions. */
|
|
77
113
|
async function readPackageManifest(targetDirectory) {
|
|
78
114
|
try {
|
|
79
115
|
const contents = await readFile(resolve(targetDirectory, 'package.json'), 'utf8');
|
|
@@ -90,15 +126,19 @@ async function readPackageManifest(targetDirectory) {
|
|
|
90
126
|
throw error;
|
|
91
127
|
}
|
|
92
128
|
}
|
|
129
|
+
/*** Escape inline-code delimiters before interpolating repository metadata into Markdown. */
|
|
93
130
|
function escapeInlineCode(value) {
|
|
94
131
|
return value.replaceAll('`', '\\`');
|
|
95
132
|
}
|
|
133
|
+
/*** Check whether an unknown failure carries a Node.js error code. */
|
|
96
134
|
function isNodeError(error) {
|
|
97
135
|
return error instanceof Error && 'code' in error;
|
|
98
136
|
}
|
|
137
|
+
/*** Check whether an unknown value is a plain record-shaped object. */
|
|
99
138
|
function isRecord(value) {
|
|
100
139
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
101
140
|
}
|
|
141
|
+
/*** Read and trim a non-empty string value from package metadata. */
|
|
102
142
|
function readNonEmptyString(value) {
|
|
103
143
|
return typeof value === 'string' && value.trim() !== '' ? value.trim() : null;
|
|
104
144
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const gitignoreManagedFiles: readonly [{
|
|
2
|
+
readonly relativePath: ".gitignore";
|
|
3
|
+
readonly render: typeof renderGitignore;
|
|
4
|
+
readonly isApplicable: typeof isGitignorePresent;
|
|
5
|
+
}];
|
|
6
|
+
/*** Preserve repository-owned ignore rules while keeping documentation directories trackable. */
|
|
7
|
+
declare function renderGitignore(targetDirectory: string): Promise<string>;
|
|
8
|
+
/*** Report whether a repository already owns a `.gitignore` file that can be normalized. */
|
|
9
|
+
declare function isGitignorePresent(targetDirectory: string): Promise<boolean>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { access, readFile } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
const TRACKED_DOCUMENTATION_PATTERNS = new Set([
|
|
4
|
+
'docs',
|
|
5
|
+
'docs/',
|
|
6
|
+
'/docs',
|
|
7
|
+
'/docs/',
|
|
8
|
+
'paradox',
|
|
9
|
+
'paradox/',
|
|
10
|
+
'/paradox',
|
|
11
|
+
'/paradox/',
|
|
12
|
+
]);
|
|
13
|
+
export const gitignoreManagedFiles = [
|
|
14
|
+
{
|
|
15
|
+
relativePath: '.gitignore',
|
|
16
|
+
render: renderGitignore,
|
|
17
|
+
isApplicable: isGitignorePresent,
|
|
18
|
+
},
|
|
19
|
+
];
|
|
20
|
+
/*** Preserve repository-owned ignore rules while keeping documentation directories trackable. */
|
|
21
|
+
async function renderGitignore(targetDirectory) {
|
|
22
|
+
const contents = await readFile(resolve(targetDirectory, '.gitignore'), 'utf8');
|
|
23
|
+
return contents
|
|
24
|
+
.split('\n')
|
|
25
|
+
.filter((line) => !TRACKED_DOCUMENTATION_PATTERNS.has(line.trim()))
|
|
26
|
+
.join('\n');
|
|
27
|
+
}
|
|
28
|
+
/*** Report whether a repository already owns a `.gitignore` file that can be normalized. */
|
|
29
|
+
async function isGitignorePresent(targetDirectory) {
|
|
30
|
+
try {
|
|
31
|
+
await access(resolve(targetDirectory, '.gitignore'));
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (isNodeError(error) && error.code === 'ENOENT')
|
|
36
|
+
return false;
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/*** Check whether an unknown failure carries a Node.js error code. */
|
|
41
|
+
function isNodeError(error) {
|
|
42
|
+
return error instanceof Error && 'code' in error;
|
|
43
|
+
}
|
|
@@ -45,6 +45,31 @@ exceptions or replaced by generic preferences from this skill.
|
|
|
45
45
|
- Keep one canonical implementation. Do not add compatibility aliases, dual paths, historical-state
|
|
46
46
|
fallbacks, or sibling-source imports.
|
|
47
47
|
|
|
48
|
+
## Comments and Paradox documentation
|
|
49
|
+
|
|
50
|
+
- Give every named production function under `src` a concise Paradox `/*** ... */` comment,
|
|
51
|
+
including exported functions and internal named helpers. Inline callbacks are exempt.
|
|
52
|
+
- Use ordinary inline comments when they explain non-obvious intent, invariants, constraints, or the
|
|
53
|
+
reason behind a decision. Do not narrate self-explanatory code or duplicate what names and types
|
|
54
|
+
already express.
|
|
55
|
+
- Paradox documentation metadata belongs only inside `/*** ... */` comments. Use only supported
|
|
56
|
+
Paradox tags: `@readme`, `@config`, `@example`, and `@usage`. Do not use JSDoc-only tags such as
|
|
57
|
+
`@param` or `@returns` as Paradox metadata.
|
|
58
|
+
- Add a supported Paradox tag only when it changes or usefully enriches generated documentation;
|
|
59
|
+
plain function descriptions do not need tags.
|
|
60
|
+
- README usage documentation must come from a real repository-root `examples/<example>/...` source
|
|
61
|
+
file. Put `@usage` in that example file's leading `/*** ... */` comment so Paradox promotes the
|
|
62
|
+
runnable example into the README Usage section. Do not create dedicated `readme-usage`,
|
|
63
|
+
`usage-readme`, `readmeUsage`, or equivalent source modules whose only purpose is feeding README
|
|
64
|
+
usage text.
|
|
65
|
+
- Update documentation sources in the pull request, including repository-owned manual documentation
|
|
66
|
+
outside the configured generated output. Never hand-edit generated README or Paradox artifacts.
|
|
67
|
+
- Generated documentation is release-owned. Ordinary feature pull requests must not regenerate or
|
|
68
|
+
commit `README.md` or the configured Paradox output. The managed release workflow runs
|
|
69
|
+
`bun run docs` after the package version bump and commits the result in the release commit.
|
|
70
|
+
- Run `bun run docs` locally only when the task explicitly changes or validates Paradox generation;
|
|
71
|
+
do not commit that generated diff from an ordinary feature pull request.
|
|
72
|
+
|
|
48
73
|
## Testing
|
|
49
74
|
|
|
50
75
|
- Test observable behavior at the owning boundary. Keep tests deterministic and independent of
|
|
@@ -60,15 +85,6 @@ exceptions or replaced by generic preferences from this skill.
|
|
|
60
85
|
full reruns without a substantive code or configuration change, stop cycling, inspect logs and
|
|
61
86
|
artifacts, and report the blocker.
|
|
62
87
|
|
|
63
|
-
## Paradox documentation
|
|
64
|
-
|
|
65
|
-
- Give every named production function under `src` a concise Paradox `/*** ... */` description,
|
|
66
|
-
including internal helpers. Inline callbacks are exempt.
|
|
67
|
-
- Add `@param`, `@returns`, `@example`, or `@readme` to public APIs only when the tag adds useful
|
|
68
|
-
information.
|
|
69
|
-
- Update documentation sources and regenerate tracked Paradox output. Never hand-edit generated
|
|
70
|
-
README or Paradox artifacts.
|
|
71
|
-
|
|
72
88
|
## Public API and release quality
|
|
73
89
|
|
|
74
90
|
- Export public symbols intentionally from canonical entrypoints and exercise those exports in
|
|
@@ -77,7 +93,8 @@ exceptions or replaced by generic preferences from this skill.
|
|
|
77
93
|
public API changes.
|
|
78
94
|
- Justify new dependencies and keep generated artifacts, lockfiles, and package metadata aligned
|
|
79
95
|
with the repository's owning workflows.
|
|
80
|
-
- Review the final diff, then run the applicable build, lint, test, typecheck, Knip, format,
|
|
81
|
-
changeset-status, and packaging checks.
|
|
96
|
+
- Review the final diff, then run the applicable build, lint, test, typecheck, Knip, format,
|
|
97
|
+
changeset-status, and packaging checks. Run documentation generation only when explicitly needed
|
|
98
|
+
to validate documentation tooling behavior.
|
|
82
99
|
- Report the exact commands run and any omissions or failures. Do not imply a fully green handoff
|
|
83
100
|
when a required check did not run or pass.
|
|
@@ -41,6 +41,13 @@ jobs:
|
|
|
41
41
|
echo "No build script found; skipping."
|
|
42
42
|
fi
|
|
43
43
|
|
|
44
|
+
- name: Validate opted-in APM owner release
|
|
45
|
+
run: |
|
|
46
|
+
if node -e "const p=require('./package.json'); process.exit(p.ankh?.apm === undefined ? 1 : 0)"; then
|
|
47
|
+
bun run test:apm
|
|
48
|
+
__ANKH_APM_RELEASE_COMMAND__ validate . --allow-owner-code
|
|
49
|
+
fi
|
|
50
|
+
|
|
44
51
|
- name: Validate Ankhorage repository
|
|
45
52
|
run: bunx @ankhorage/ankh doctor validate .
|
|
46
53
|
|
|
@@ -73,8 +73,19 @@ jobs:
|
|
|
73
73
|
|
|
74
74
|
before_version="$(node -p "require('./package.json').version")"
|
|
75
75
|
__ANKH_CHANGESETS_VERSION_COMMAND__
|
|
76
|
+
if node -e "const p=require('./package.json'); process.exit(p.ankh?.apm === undefined ? 1 : 0)"; then
|
|
77
|
+
__ANKH_APM_RELEASE_COMMAND__ sync .
|
|
78
|
+
fi
|
|
76
79
|
after_version="$(node -p "require('./package.json').version")"
|
|
77
80
|
|
|
81
|
+
if [ "$before_version" != "$after_version" ]; then
|
|
82
|
+
if node -e "const p=require('./package.json'); process.exit(p.scripts?.docs ? 0 : 1)"; then
|
|
83
|
+
bun run docs
|
|
84
|
+
else
|
|
85
|
+
echo "No docs script found; skipping."
|
|
86
|
+
fi
|
|
87
|
+
fi
|
|
88
|
+
|
|
78
89
|
if git diff --quiet; then
|
|
79
90
|
echo "versioned=false" >> "$GITHUB_OUTPUT"
|
|
80
91
|
exit 0
|
|
@@ -100,7 +111,30 @@ jobs:
|
|
|
100
111
|
|
|
101
112
|
- name: Publish unpublished packages
|
|
102
113
|
if: hashFiles('.changeset/config.json') != ''
|
|
103
|
-
run:
|
|
114
|
+
run: |
|
|
115
|
+
if ! node -e "const p=require('./package.json'); process.exit(p.ankh?.apm === undefined ? 1 : 0)"; then
|
|
116
|
+
__ANKH_CHANGESETS_PUBLISH_COMMAND__
|
|
117
|
+
exit 0
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
package_name="$(node -p "require('./package.json').name")"
|
|
121
|
+
package_version="$(node -p "require('./package.json').version")"
|
|
122
|
+
registry_evidence="${RUNNER_TEMP}/apm-registry.json"
|
|
123
|
+
if npm view "${package_name}@${package_version}" version --json > "$registry_evidence"; then
|
|
124
|
+
echo "Exact APM owner version is already published."
|
|
125
|
+
else
|
|
126
|
+
# Authentication/network errors must never be treated as an unpublished version.
|
|
127
|
+
node -e "const p=require(process.argv[1]); if(p.error?.code !== 'E404') process.exit(1)" "$registry_evidence"
|
|
128
|
+
bun install --frozen-lockfile --ignore-scripts
|
|
129
|
+
bun run build
|
|
130
|
+
bun run test:apm
|
|
131
|
+
artifact="$(mktemp -d "${RUNNER_TEMP}/apm-publish.XXXXXX")/candidate.tgz"
|
|
132
|
+
__ANKH_APM_RELEASE_COMMAND__ validate . --allow-owner-code --artifact "$artifact"
|
|
133
|
+
npm publish "$artifact" --ignore-scripts
|
|
134
|
+
fi
|
|
135
|
+
if ! git rev-parse -q --verify "refs/tags/v${package_version}" >/dev/null; then
|
|
136
|
+
git tag "v${package_version}"
|
|
137
|
+
fi
|
|
104
138
|
|
|
105
139
|
- name: Finalize Changesets v3 tags and GitHub releases
|
|
106
140
|
if: hashFiles('.changeset/config.json') != ''
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { access } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
+
import { resolveApmReleaseCommandAsync } from '../../features/apm-release-validation/adapters/outbound/resolveApmReleaseCommandAsync.js';
|
|
3
4
|
import { bunRuntimePolicy, nodeRuntimePolicy } from '../../policy/bunRuntimePolicy.js';
|
|
4
5
|
import { renderRenovateWorkflowAsync } from './renderRenovateWorkflowAsync.js';
|
|
5
6
|
import { renderWorkflowAsync } from './renderWorkflowAsync.js';
|
|
@@ -30,7 +31,8 @@ function createWorkflowDefinition(relativePath, sourcePath) {
|
|
|
30
31
|
const sourceUrl = new URL(sourcePath, import.meta.url);
|
|
31
32
|
return {
|
|
32
33
|
relativePath,
|
|
33
|
-
render: async () => await renderWorkflowAsync(sourceUrl, {
|
|
34
|
+
render: async (targetDirectory) => await renderWorkflowAsync(sourceUrl, {
|
|
35
|
+
apmReleaseCommand: await resolveApmReleaseCommandAsync(targetDirectory),
|
|
34
36
|
bunVersion: bunRuntimePolicy.version,
|
|
35
37
|
nodeVersion: nodeRuntimePolicy.setupVersion,
|
|
36
38
|
}),
|
|
@@ -42,6 +44,7 @@ function createRenovateWorkflowDefinition() {
|
|
|
42
44
|
return {
|
|
43
45
|
relativePath: '.github/workflows/renovate.yml',
|
|
44
46
|
render: async (targetDirectory) => await renderRenovateWorkflowAsync(sourceUrl, targetDirectory, {
|
|
47
|
+
apmReleaseCommand: await resolveApmReleaseCommandAsync(targetDirectory),
|
|
45
48
|
bunVersion: bunRuntimePolicy.version,
|
|
46
49
|
nodeVersion: nodeRuntimePolicy.setupVersion,
|
|
47
50
|
}),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export interface WorkflowPolicy {
|
|
2
2
|
readonly bunVersion: string;
|
|
3
3
|
readonly nodeVersion: string;
|
|
4
|
+
readonly apmReleaseCommand?: string;
|
|
4
5
|
}
|
|
5
6
|
/*** Renders one workflow template with the canonical runtime and Changesets policy. */
|
|
6
7
|
export declare function renderWorkflowAsync(sourceUrl: URL, policy: WorkflowPolicy): Promise<string>;
|
|
@@ -8,6 +8,7 @@ export async function renderWorkflowAsync(sourceUrl, policy) {
|
|
|
8
8
|
.replaceAll(CHANGESETS_PUBLISH_COMMAND_TOKEN, changesetsPolicy.workflowCommands.publish)
|
|
9
9
|
.replaceAll(CHANGESETS_STATUS_COMMAND_TOKEN, changesetsPolicy.workflowCommands.status)
|
|
10
10
|
.replaceAll(CHANGESETS_VERSION_COMMAND_TOKEN, changesetsPolicy.workflowCommands.version)
|
|
11
|
+
.replaceAll('__ANKH_APM_RELEASE_COMMAND__', policy.apmReleaseCommand ?? './node_modules/.bin/ankhorage-apm-release')
|
|
11
12
|
.replaceAll(NODE_VERSION_TOKEN, policy.nodeVersion);
|
|
12
13
|
}
|
|
13
14
|
const BUN_VERSION_TOKEN = '__ANKH_BUN_VERSION__';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ApmUpdateDescriptor } from '@ankhorage/apm/types';
|
|
2
|
+
/*** Packed bytes and loaded owner evidence supplied to the canonical APM validators. */
|
|
3
|
+
export interface ApmPackedReleaseCandidate {
|
|
4
|
+
readonly packageJson: Readonly<Record<string, unknown>>;
|
|
5
|
+
readonly descriptor: unknown;
|
|
6
|
+
readonly descriptorSource: string;
|
|
7
|
+
readonly integrity: string;
|
|
8
|
+
readonly extension?: unknown;
|
|
9
|
+
}
|
|
10
|
+
/*** Explicit release-validation inputs; owner code is never trusted by default. */
|
|
11
|
+
export interface ApmReleaseValidationOptions {
|
|
12
|
+
readonly allowOwnerCode?: boolean;
|
|
13
|
+
readonly artifactPath?: string;
|
|
14
|
+
readonly expectedIntegrity?: string;
|
|
15
|
+
readonly previousDescriptors?: readonly ApmUpdateDescriptor[];
|
|
16
|
+
readonly relatedDescriptors?: readonly ApmUpdateDescriptor[];
|
|
17
|
+
}
|
|
18
|
+
/*** Evidence for one exact packed release; inapplicable never means migration-safe. */
|
|
19
|
+
export interface ApmReleaseValidationResult {
|
|
20
|
+
readonly applicable: boolean;
|
|
21
|
+
readonly valid: boolean;
|
|
22
|
+
readonly packageName?: string;
|
|
23
|
+
readonly packageVersion?: string;
|
|
24
|
+
readonly integrity?: string;
|
|
25
|
+
readonly artifactPath?: string;
|
|
26
|
+
readonly blockers: readonly string[];
|
|
27
|
+
}
|
|
28
|
+
/*** Filesystem capabilities required by descriptor synchronization, independent of transport. */
|
|
29
|
+
export interface ApmReleaseDescriptorPort {
|
|
30
|
+
readonly readPackageAsync: () => Promise<Readonly<Record<string, unknown>>>;
|
|
31
|
+
readonly readDescriptorAsync: (subpath: string) => Promise<Readonly<Record<string, unknown>>>;
|
|
32
|
+
readonly writeDescriptorAsync: (subpath: string, descriptor: Readonly<Record<string, unknown>>) => Promise<boolean>;
|
|
33
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { ApmPackedReleaseCandidate, ApmReleaseValidationOptions, ApmReleaseValidationResult, } from './apm-release-validation.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|