@ankhorage/devtools 1.10.13 → 1.10.15

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.
@@ -4,46 +4,52 @@ import { access, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
4
4
  import { join, relative, resolve, sep } from 'node:path';
5
5
  import { pathToFileURL } from 'node:url';
6
6
 
7
- import { generateTemplateCatalog } from './generate-template-catalog.mjs';
8
- import { loadOwnerApis } from './owner-api.mjs';
7
+ import { generateTemplateCatalog } from './generate-template-catalog.ts';
8
+ import { loadOwnerApis } from './owner-api.ts';
9
9
 
10
10
  /*** Scaffold one complete portable template and refresh filesystem discovery. */
11
- export async function scaffoldTemplate(input) {
11
+ export async function scaffoldTemplate(input: unknown) {
12
12
  assertRecord(input, 'Scaffold input');
13
- for (const field of ['targetDirectory', 'category', 'slug']) {
14
- assertNonEmptyString(input[field], field);
15
- }
16
- if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(input.slug)) {
13
+ assertNonEmptyString(input.targetDirectory, 'targetDirectory');
14
+ assertNonEmptyString(input.category, 'category');
15
+ assertNonEmptyString(input.slug, 'slug');
16
+ const { category, slug, targetDirectory: inputTargetDirectory } = input;
17
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(slug)) {
17
18
  throw new Error('slug must be a kebab-case identifier.');
18
19
  }
19
20
  assertRecord(input.manifest, 'manifest');
21
+ const { manifest: inputManifest } = input;
20
22
 
21
- const targetDirectory = resolve(input.targetDirectory);
22
- const packageManifest = JSON.parse(await readFile(join(targetDirectory, 'package.json'), 'utf8'));
23
+ const targetDirectory = resolve(inputTargetDirectory);
24
+ const packageManifest: unknown = JSON.parse(
25
+ await readFile(join(targetDirectory, 'package.json'), 'utf8'),
26
+ );
27
+ assertRecord(packageManifest, 'Target package manifest');
23
28
  if (packageManifest.name !== '@ankhorage/templates') {
24
29
  throw new Error(
25
30
  'Template scaffolding is available only in the @ankhorage/templates repository.',
26
31
  );
27
32
  }
28
- if (input.manifest.metadata?.category !== input.category) {
33
+ assertRecord(inputManifest.metadata, 'manifest.metadata');
34
+ if (inputManifest.metadata.category !== category) {
29
35
  throw new Error('Scaffold category must match manifest.metadata.category.');
30
36
  }
31
- if (input.manifest.metadata?.slug !== input.slug) {
37
+ if (inputManifest.metadata.slug !== slug) {
32
38
  throw new Error('Scaffold slug must match manifest.metadata.slug.');
33
39
  }
34
40
 
35
41
  const owners = await loadOwnerApis(targetDirectory);
36
- const composition = owners.templates.validateTemplateManifest(input.manifest, 'release');
42
+ const composition = owners.templates.validateTemplateManifest(inputManifest, 'release');
37
43
  const manifest = owners.templates.assertTemplateManifestReady(composition);
38
44
 
39
45
  const categoryDirectory = resolve(
40
46
  targetDirectory,
41
47
  'src/templates/categories',
42
- input.category.replaceAll('_', '-'),
48
+ category.replaceAll('_', '-'),
43
49
  );
44
50
  assertInside(targetDirectory, categoryDirectory);
45
51
 
46
- const templateDirectory = resolve(categoryDirectory, input.slug);
52
+ const templateDirectory = resolve(categoryDirectory, slug);
47
53
  assertInside(categoryDirectory, templateDirectory);
48
54
  if (await pathExists(templateDirectory)) {
49
55
  throw new Error(
@@ -75,7 +81,7 @@ export async function scaffoldTemplate(input) {
75
81
  }
76
82
 
77
83
  /*** Serialize one complete manifest as the template's canonical default export. */
78
- function createManifestSource(manifest) {
84
+ function createManifestSource(manifest: Record<string, unknown>): string {
79
85
  return `import type { AppManifest } from '@ankhorage/contracts';
80
86
 
81
87
  const manifest = ${JSON.stringify(manifest, null, 2)} satisfies AppManifest;
@@ -88,7 +94,7 @@ export default function createAppManifest(): AppManifest {
88
94
  }
89
95
 
90
96
  /*** Assert that a resolved output remains inside its declared owner directory. */
91
- function assertInside(parentPath, childPath) {
97
+ function assertInside(parentPath: string, childPath: string): void {
92
98
  const relativePath = relative(parentPath, childPath);
93
99
  if (
94
100
  relativePath === '' ||
@@ -101,7 +107,7 @@ function assertInside(parentPath, childPath) {
101
107
  }
102
108
 
103
109
  /*** Return whether a filesystem path exists. */
104
- async function pathExists(filePath) {
110
+ async function pathExists(filePath: string): Promise<boolean> {
105
111
  try {
106
112
  await access(filePath);
107
113
  return true;
@@ -111,14 +117,14 @@ async function pathExists(filePath) {
111
117
  }
112
118
 
113
119
  /*** Require a non-array object input. */
114
- function assertRecord(value, label) {
120
+ function assertRecord(value: unknown, label: string): asserts value is Record<string, unknown> {
115
121
  if (typeof value !== 'object' || value === null || Array.isArray(value)) {
116
122
  throw new Error(`${label} must be an object.`);
117
123
  }
118
124
  }
119
125
 
120
126
  /*** Require a non-empty string input field. */
121
- function assertNonEmptyString(value, label) {
127
+ function assertNonEmptyString(value: unknown, label: string): asserts value is string {
122
128
  if (typeof value !== 'string' || value.trim() === '') {
123
129
  throw new Error(`${label} must be a non-empty string.`);
124
130
  }
@@ -128,9 +134,9 @@ function assertNonEmptyString(value, label) {
128
134
  async function main() {
129
135
  const [inputPath] = process.argv.slice(2);
130
136
  if (!inputPath) {
131
- throw new Error('Usage: scaffold-template.mjs <scaffold-input.json>');
137
+ throw new Error('Usage: scaffold-template.ts <scaffold-input.json>');
132
138
  }
133
- const input = JSON.parse(await readFile(resolve(inputPath), 'utf8'));
139
+ const input: unknown = JSON.parse(await readFile(resolve(inputPath), 'utf8'));
134
140
  console.log(JSON.stringify(await scaffoldTemplate(input), null, 2));
135
141
  }
136
142
 
@@ -50,6 +50,7 @@ jobs:
50
50
  fi
51
51
 
52
52
  - name: Create release pull request or publish to npm
53
+ id: changesets
53
54
  if: hashFiles('.changeset/config.json') != ''
54
55
  uses: changesets/action@v1
55
56
  with:
@@ -76,6 +77,40 @@ jobs:
76
77
  fi
77
78
  done
78
79
 
80
+ - name: Create the scoped rollout token
81
+ id: rollout-token
82
+ if: steps.changesets.outputs.published == 'true'
83
+ uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
84
+ with:
85
+ client-id: ${{ vars.ANKHORAGE_RENOVATE_SYNC_CLIENT_ID }}
86
+ private-key: ${{ secrets.ANKHORAGE_RENOVATE_SYNC_PRIVATE_KEY }}
87
+ owner: ankhorage
88
+ repositories: renovate
89
+ permission-contents: write
90
+
91
+ - name: Trigger the Devtools consumer rollout
92
+ if: steps.changesets.outputs.published == 'true'
93
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
94
+ env:
95
+ PUBLISHED_PACKAGES: ${{ steps.changesets.outputs.publishedPackages }}
96
+ with:
97
+ github-token: ${{ steps.rollout-token.outputs.token }}
98
+ script: |
99
+ const publishedPackages = JSON.parse(process.env.PUBLISHED_PACKAGES ?? '[]');
100
+ const release = publishedPackages.find(
101
+ (candidate) => candidate.name === '@ankhorage/devtools',
102
+ );
103
+ if (!release || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(release.version)) {
104
+ throw new Error('Changesets must report one exact published Devtools version.');
105
+ }
106
+
107
+ await github.rest.repos.createDispatchEvent({
108
+ owner: 'ankhorage',
109
+ repo: 'renovate',
110
+ event_type: 'devtools-release',
111
+ client_payload: { version: release.version },
112
+ });
113
+
79
114
  - name: Skip release
80
115
  if: hashFiles('.changeset/config.json') == ''
81
116
  run: echo "No Changesets config found; skipping release."
@@ -14,10 +14,10 @@ permissions:
14
14
  jobs:
15
15
  changeset:
16
16
  if: >-
17
- github.actor == 'renovate[bot]' &&
17
+ (github.actor == 'renovate[bot]' || github.actor == 'ankhorage-renovate-sync[bot]') &&
18
18
  github.event.pull_request.head.repo.full_name == github.repository &&
19
19
  startsWith(github.event.pull_request.head.ref, 'renovate/')
20
- uses: ankhorage/renovate/.github/workflows/changeset.yml@1721d245371e879301d7a2e5299d1c5790d97459
20
+ uses: ankhorage/renovate/.github/workflows/changeset.yml@d3f138f4e8d3eb84244730f86591c6556738c1f4
21
21
  with:
22
22
  renovate_sync_client_id: ${{ vars.ANKHORAGE_RENOVATE_SYNC_CLIENT_ID }}
23
23
  secrets:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ankhorage/devtools",
3
- "version": "1.10.13",
3
+ "version": "1.10.15",
4
4
  "description": "Shared development tools and repository standards for Ankhorage",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/ankhorage/devtools#readme",