@smoothbricks/cli 0.10.1 → 0.10.3
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 +25 -13
- package/dist/cli.js +24 -0
- package/dist/github-ci/index.d.ts +17 -2
- package/dist/github-ci/index.d.ts.map +1 -1
- package/dist/github-ci/index.js +145 -14
- package/dist/github-ci/outputs.d.ts +22 -0
- package/dist/github-ci/outputs.d.ts.map +1 -0
- package/dist/github-ci/outputs.js +649 -0
- package/dist/lib/conflict-markers.d.ts.map +1 -1
- package/dist/lib/workspace.d.ts +1 -1
- package/dist/lib/workspace.d.ts.map +1 -1
- package/dist/monorepo/ci-workflow.d.ts.map +1 -1
- package/dist/monorepo/ci-workflow.js +2 -1
- package/dist/monorepo/managed-files.d.ts +2 -0
- package/dist/monorepo/managed-files.d.ts.map +1 -1
- package/dist/monorepo/managed-files.js +50 -5
- package/dist/monorepo/package-policy.d.ts.map +1 -1
- package/dist/monorepo/package-policy.js +26 -0
- package/dist/monorepo/packs/index.d.ts +3 -0
- package/dist/monorepo/packs/index.d.ts.map +1 -1
- package/dist/monorepo/packs/index.js +5 -2
- package/dist/monorepo/publish-workflow.d.ts +1 -0
- package/dist/monorepo/publish-workflow.d.ts.map +1 -1
- package/dist/monorepo/publish-workflow.js +240 -2
- package/dist/monorepo/tool-validation.d.ts.map +1 -1
- package/dist/monorepo/tool-validation.js +20 -7
- package/dist/nx/index.d.ts +6 -0
- package/dist/nx/index.d.ts.map +1 -1
- package/dist/nx/index.js +55 -5
- package/dist/release/github-release.d.ts +5 -2
- package/dist/release/github-release.d.ts.map +1 -1
- package/dist/release/github-release.js +11 -0
- package/dist/release/index.d.ts +19 -1
- package/dist/release/index.d.ts.map +1 -1
- package/dist/release/index.js +140 -31
- package/dist/release/orchestration.d.ts +8 -1
- package/dist/release/orchestration.d.ts.map +1 -1
- package/dist/release/orchestration.js +38 -1
- package/dist/wrangler/prepare-env.d.ts.map +1 -1
- package/dist/wrangler/prepare-env.js +1 -1
- package/dist/wrangler/scaffold.d.ts.map +1 -1
- package/dist/wrangler/scaffold.js +1 -1
- package/managed/raw/tooling/direnv/github-actions-bootstrap.sh +5 -0
- package/managed/raw/tooling/direnv/setup-environment.ts +32 -1
- package/managed/templates/github/actions/cache-nix-devenv/action.yml +12 -10
- package/managed/templates/github/actions/cache-node-modules/action.yml +3 -2
- package/managed/templates/github/actions/cache-nx/action.yml +2 -2
- package/managed/templates/github/actions/save-nix-devenv/action.yml +11 -3
- package/package.json +7 -14
- package/src/cli.ts +30 -4
- package/src/github-ci/index.test.ts +486 -0
- package/src/github-ci/index.ts +177 -16
- package/src/github-ci/outputs.ts +506 -0
- package/src/monorepo/__tests__/ci-workflow.test.ts +13 -0
- package/src/monorepo/__tests__/publish-workflow.test.ts +234 -5
- package/src/monorepo/ci-workflow.ts +3 -1
- package/src/monorepo/managed-files.test.ts +75 -0
- package/src/monorepo/managed-files.ts +61 -5
- package/src/monorepo/package-policy.test.ts +57 -38
- package/src/monorepo/package-policy.ts +28 -0
- package/src/monorepo/packs/index.test.ts +18 -1
- package/src/monorepo/packs/index.ts +7 -3
- package/src/monorepo/publish-workflow.ts +481 -2
- package/src/monorepo/tool-validation.test.ts +59 -24
- package/src/monorepo/tool-validation.ts +21 -7
- package/src/monorepo/wrangler.test.ts +9 -2
- package/src/nx/index.test.ts +39 -0
- package/src/nx/index.ts +65 -5
- package/src/release/__tests__/fixture-repo.test.ts +18 -16
- package/src/release/__tests__/github-release.test.ts +11 -0
- package/src/release/__tests__/orchestration.test.ts +57 -2
- package/src/release/__tests__/trust-publisher.test.ts +91 -2
- package/src/release/github-release.ts +12 -0
- package/src/release/index.ts +218 -40
- package/src/release/orchestration.ts +55 -3
- package/src/wrangler/prepare-env.ts +14 -4
- package/src/wrangler/scaffold.ts +8 -3
|
@@ -1,6 +1,14 @@
|
|
|
1
|
+
/* biome-ignore-all lint/suspicious/noTemplateCurlyInString: Assertions cover literal GitHub Actions expressions. */
|
|
2
|
+
|
|
1
3
|
import { describe, expect, it } from 'bun:test';
|
|
2
4
|
import { readFile } from 'node:fs/promises';
|
|
3
5
|
import { join } from 'node:path';
|
|
6
|
+
import {
|
|
7
|
+
LINUX_PLATFORM_TARGET_GLOBS,
|
|
8
|
+
MACOS_PLATFORM_TARGET_GLOBS,
|
|
9
|
+
PLATFORM_TARGET_GLOBS,
|
|
10
|
+
} from '@smoothbricks/nx-plugin/workspace-config-policy';
|
|
11
|
+
import { format } from 'prettier';
|
|
4
12
|
import {
|
|
5
13
|
definePublishWorkflow,
|
|
6
14
|
type PublishWorkflowBump,
|
|
@@ -13,13 +21,208 @@ import {
|
|
|
13
21
|
|
|
14
22
|
describe('publish workflow definition', () => {
|
|
15
23
|
it('renders the checked-in local publish workflow copy', async () => {
|
|
16
|
-
const rendered = renderPublishWorkflowYaml({
|
|
24
|
+
const rendered = renderPublishWorkflowYaml({
|
|
25
|
+
repoName: '@smoothbricks/codebase',
|
|
26
|
+
platformTargetGlobs: PLATFORM_TARGET_GLOBS,
|
|
27
|
+
});
|
|
17
28
|
const packageRoot = join(import.meta.dir, '..', '..', '..');
|
|
18
29
|
await expect(readFile(join(packageRoot, '..', '..', '.github/workflows/publish.yml'), 'utf8')).resolves.toBe(
|
|
19
30
|
rendered,
|
|
20
31
|
);
|
|
21
32
|
});
|
|
22
33
|
|
|
34
|
+
it('renders workflow bytes that are stable under the repository Prettier config', async () => {
|
|
35
|
+
const rendered = renderPublishWorkflowYaml({
|
|
36
|
+
repoName: '@smoothbricks/codebase',
|
|
37
|
+
platformTargetGlobs: PLATFORM_TARGET_GLOBS,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
await expect(
|
|
41
|
+
format(rendered, {
|
|
42
|
+
parser: 'yaml',
|
|
43
|
+
printWidth: 120,
|
|
44
|
+
proseWrap: 'always',
|
|
45
|
+
}),
|
|
46
|
+
).resolves.toBe(rendered);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('preserves the single Ubuntu job and renders no artifact transfer when no Apple targets exist', () => {
|
|
50
|
+
const current = renderPublishWorkflowYaml({ repoName: '@smoothbricks/codebase' });
|
|
51
|
+
const explicitlyEmpty = renderPublishWorkflowYaml({
|
|
52
|
+
repoName: '@smoothbricks/codebase',
|
|
53
|
+
platformTargetGlobs: [],
|
|
54
|
+
});
|
|
55
|
+
const linuxOnly = renderPublishWorkflowYaml({
|
|
56
|
+
repoName: '@smoothbricks/codebase',
|
|
57
|
+
platformTargetGlobs: LINUX_PLATFORM_TARGET_GLOBS,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
expect(explicitlyEmpty).toBe(current);
|
|
61
|
+
for (const rendered of [current, linuxOnly]) {
|
|
62
|
+
const jobsYaml = rendered.slice(rendered.indexOf('jobs:\n') + 'jobs:\n'.length);
|
|
63
|
+
expect(jobsYaml).toContain(' publish:\n runs-on: ubuntu-latest');
|
|
64
|
+
expect(jobsYaml.match(/^ {2}[a-z][a-z-]+:\n/gm)).toHaveLength(1);
|
|
65
|
+
expect(rendered).not.toContain('linux-release-candidate:');
|
|
66
|
+
expect(rendered).not.toContain('macos-platform:');
|
|
67
|
+
expect(rendered).not.toContain('publish-on-linux:');
|
|
68
|
+
expect(rendered).not.toContain('--collect-outputs');
|
|
69
|
+
expect(rendered).not.toContain('Upload validated release state');
|
|
70
|
+
expect(rendered).not.toContain('Upload validated build outputs');
|
|
71
|
+
expect(rendered).not.toContain('Upload supplemental Linux outputs');
|
|
72
|
+
expect(rendered).not.toContain('Upload macOS platform outputs');
|
|
73
|
+
expect(rendered).not.toContain('actions/download-artifact');
|
|
74
|
+
expect(rendered).not.toContain('apply-outputs');
|
|
75
|
+
expect(rendered).not.toContain('git bundle');
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('keeps job-local deeplink step anchors exact for single-job and parallel platform workflows', () => {
|
|
80
|
+
const singleJob = renderPublishWorkflowYaml({ repoName: '@smoothbricks/codebase' });
|
|
81
|
+
const platform = renderPublishWorkflowYaml({
|
|
82
|
+
deploy: true,
|
|
83
|
+
repoName: '@smoothbricks/codebase',
|
|
84
|
+
platformTargetGlobs: PLATFORM_TARGET_GLOBS,
|
|
85
|
+
});
|
|
86
|
+
const linuxCandidate = platform.slice(
|
|
87
|
+
platform.indexOf(' linux-release-candidate:'),
|
|
88
|
+
platform.indexOf(' macos-platform:'),
|
|
89
|
+
);
|
|
90
|
+
const macosPlatform = platform.slice(
|
|
91
|
+
platform.indexOf(' macos-platform:'),
|
|
92
|
+
platform.indexOf(' publish-on-linux:'),
|
|
93
|
+
);
|
|
94
|
+
const finalJob = platform.slice(platform.indexOf(' publish-on-linux:'));
|
|
95
|
+
|
|
96
|
+
expect(stepAnchorNumbers(singleJob)).toEqual(Array.from({ length: 15 }, (_, index) => index + 1));
|
|
97
|
+
expect(stepAnchorNumbers(linuxCandidate)).toEqual(Array.from({ length: 19 }, (_, index) => index + 1));
|
|
98
|
+
expect(stepAnchorNumbers(macosPlatform)).toEqual(Array.from({ length: 7 }, (_, index) => index + 1));
|
|
99
|
+
expect(stepAnchorNumbers(finalJob)).toEqual(Array.from({ length: 14 }, (_, index) => index + 1));
|
|
100
|
+
expect(singleJob).toContain('# Step 14\n - name: 📦 Publish release (${{ steps.version.outputs.mode }})');
|
|
101
|
+
expect(singleJob).toContain('# Step 15\n - name: 🧹 Cleanup and cache Nix/devenv');
|
|
102
|
+
expect(linuxCandidate).toContain('# Step 7\n - name: 🔒 Capture candidate release SHA');
|
|
103
|
+
expect(linuxCandidate).toContain(
|
|
104
|
+
'# Step 8\n - name: ✅ Check managed monorepo files (${{ steps.version.outputs.mode }})',
|
|
105
|
+
);
|
|
106
|
+
expect(linuxCandidate).toContain('# Step 19\n - name: 🧹 Cleanup and cache Nix/devenv');
|
|
107
|
+
expect(macosPlatform).toContain('# Step 5\n - name: 🍎 Build selected macOS and iOS release outputs');
|
|
108
|
+
expect(macosPlatform).toContain('# Step 7\n - name: 🧹 Cleanup and cache Nix/devenv');
|
|
109
|
+
expect(finalJob).toContain('# Step 7\n - name: 🧯 Repair pending releases');
|
|
110
|
+
expect(finalJob).toContain('# Step 8\n - name: ♻️ Restore validated release state');
|
|
111
|
+
expect(finalJob).toContain('# Step 9\n - name: 📦 Apply verified Linux outputs');
|
|
112
|
+
expect(finalJob).toContain('# Step 10\n - name: 🍎 Apply verified macOS outputs');
|
|
113
|
+
expect(finalJob).toContain(
|
|
114
|
+
'# Step 12\n - name: 📦 Publish release (${{ needs.linux-release-candidate.outputs.mode }})',
|
|
115
|
+
);
|
|
116
|
+
expect(finalJob).toContain('# Step 13\n - name: 🚀 Deploy production');
|
|
117
|
+
expect(finalJob).toContain('# Step 14\n - name: 🧹 Cleanup and cache Nix/devenv');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('renders parallel native producers and a dependent publish-only final job', () => {
|
|
121
|
+
const linuxOnly = renderPublishWorkflowYaml({
|
|
122
|
+
repoName: '@smoothbricks/codebase',
|
|
123
|
+
platformTargetGlobs: LINUX_PLATFORM_TARGET_GLOBS,
|
|
124
|
+
});
|
|
125
|
+
const native = renderPublishWorkflowYaml({
|
|
126
|
+
repoName: '@smoothbricks/codebase',
|
|
127
|
+
platformTargetGlobs: PLATFORM_TARGET_GLOBS,
|
|
128
|
+
});
|
|
129
|
+
const linuxCandidate = native.slice(
|
|
130
|
+
native.indexOf(' linux-release-candidate:'),
|
|
131
|
+
native.indexOf(' macos-platform:'),
|
|
132
|
+
);
|
|
133
|
+
const macosPlatform = native.slice(native.indexOf(' macos-platform:'), native.indexOf(' publish-on-linux:'));
|
|
134
|
+
const finalJob = native.slice(native.indexOf(' publish-on-linux:'));
|
|
135
|
+
|
|
136
|
+
expect(linuxOnly).not.toContain('macos-platform:');
|
|
137
|
+
expect(linuxOnly).toContain(
|
|
138
|
+
`smoo github-ci nx-run-many --targets "${LINUX_PLATFORM_TARGET_GLOBS.join(',')}" --projects`,
|
|
139
|
+
);
|
|
140
|
+
expect(native).toContain(' linux-release-candidate:\n runs-on: ubuntu-latest');
|
|
141
|
+
expect(native).toContain(' macos-platform:\n runs-on: macos-latest');
|
|
142
|
+
expect(native).toContain(' publish-on-linux:\n needs: [linux-release-candidate, macos-platform]');
|
|
143
|
+
expect(linuxCandidate).not.toContain('needs:');
|
|
144
|
+
expect(macosPlatform).not.toContain('needs:');
|
|
145
|
+
expect(linuxCandidate).not.toContain('smoo release repair-pending');
|
|
146
|
+
expect(linuxCandidate).toContain('smoo release version');
|
|
147
|
+
expect(linuxCandidate).toContain('smoo github-ci nx-run-many --targets build --projects');
|
|
148
|
+
expect(linuxCandidate).toContain('smoo github-ci nx-run-many --targets lint --projects');
|
|
149
|
+
expect(linuxCandidate).toContain('smoo github-ci nx-run-many --targets test --projects');
|
|
150
|
+
expect(linuxCandidate).toContain(
|
|
151
|
+
`smoo github-ci nx-run-many --targets "${LINUX_PLATFORM_TARGET_GLOBS.join(',')}" --projects`,
|
|
152
|
+
);
|
|
153
|
+
expect(macosPlatform).not.toContain('smoo github-ci nx-run-many');
|
|
154
|
+
expect(foldedRunCommand(macosPlatform, '🍎 Build selected macOS and iOS release outputs')).toBe(
|
|
155
|
+
`smoo release build-platform-outputs --bump "\${{ inputs.bump }}" --ref "\${{ github.sha }}" --targets "${MACOS_PLATFORM_TARGET_GLOBS.join(
|
|
156
|
+
',',
|
|
157
|
+
)}" --output "\${{ runner.temp }}/macos-platform-outputs"`,
|
|
158
|
+
);
|
|
159
|
+
expect(finalJob).not.toContain('smoo release version');
|
|
160
|
+
expect(finalJob).not.toContain('nx-run-many');
|
|
161
|
+
expect(foldedRunCommand(finalJob, '🧯 Repair pending releases')).toBe(
|
|
162
|
+
'smoo release repair-pending --ref "${{ github.sha }}" --platform-outputs ' +
|
|
163
|
+
'"${{ runner.temp }}/publish-artifacts/publish-macos-outputs-${{ github.run_id }}/repairs" ' +
|
|
164
|
+
'--dry-run "${{ inputs.dry_run }}"',
|
|
165
|
+
);
|
|
166
|
+
expect(native).toContain('uses: actions/upload-artifact@v7.0.1');
|
|
167
|
+
expect(native).toContain('uses: actions/download-artifact@v8.0.1');
|
|
168
|
+
expect(native).toContain('name: publish-release-state-${{ github.run_id }}');
|
|
169
|
+
expect(native).toContain('name: publish-release-outputs-${{ github.run_id }}');
|
|
170
|
+
expect(native).toContain('name: publish-linux-outputs-${{ github.run_id }}');
|
|
171
|
+
expect(native.match(/include-hidden-files: true/g)).toHaveLength(4);
|
|
172
|
+
expect(native).toContain('name: publish-macos-outputs-${{ github.run_id }}');
|
|
173
|
+
expect(native).toContain('git bundle create');
|
|
174
|
+
expect(native).toContain('git fetch "${{ runner.temp }}/publish-artifacts/publish-release-state-');
|
|
175
|
+
expect(finalJob).toContain(
|
|
176
|
+
'smoo github-ci apply-outputs --source-sha "${{ needs.linux-release-candidate.outputs.release-sha }}"',
|
|
177
|
+
);
|
|
178
|
+
expect(finalJob).toContain('smoo github-ci apply-outputs --source-sha "${{ github.sha }}"');
|
|
179
|
+
expect(foldedRunCommand(finalJob, '📦 Apply verified Linux outputs')).toBe(
|
|
180
|
+
'smoo github-ci apply-outputs --source-sha "${{ needs.linux-release-candidate.outputs.release-sha }}" ' +
|
|
181
|
+
'"${{ runner.temp }}/publish-artifacts/publish-release-outputs-${{ github.run_id }}" ' +
|
|
182
|
+
'"${{ runner.temp }}/publish-artifacts/publish-linux-outputs-${{ github.run_id }}"',
|
|
183
|
+
);
|
|
184
|
+
expect(foldedRunCommand(finalJob, '🍎 Apply verified macOS outputs')).toBe(
|
|
185
|
+
'smoo github-ci apply-outputs --source-sha "${{ github.sha }}" ' +
|
|
186
|
+
'"${{ runner.temp }}/publish-artifacts/publish-macos-outputs-${{ github.run_id }}/current"',
|
|
187
|
+
);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('limits trusted publishing permission to final publish and preserves mode, deploy, and dry-run gates', () => {
|
|
191
|
+
const rendered = renderPublishWorkflowYaml({
|
|
192
|
+
deploy: true,
|
|
193
|
+
deployProvider: 'cloudflare',
|
|
194
|
+
repoName: '@smoothbricks/codebase',
|
|
195
|
+
platformTargetGlobs: PLATFORM_TARGET_GLOBS,
|
|
196
|
+
});
|
|
197
|
+
const linuxCandidate = rendered.slice(
|
|
198
|
+
rendered.indexOf(' linux-release-candidate:'),
|
|
199
|
+
rendered.indexOf(' macos-platform:'),
|
|
200
|
+
);
|
|
201
|
+
const macosPlatform = rendered.slice(
|
|
202
|
+
rendered.indexOf(' macos-platform:'),
|
|
203
|
+
rendered.indexOf(' publish-on-linux:'),
|
|
204
|
+
);
|
|
205
|
+
const finalJob = rendered.slice(rendered.indexOf(' publish-on-linux:'));
|
|
206
|
+
|
|
207
|
+
expect(linuxCandidate).toContain('id-token: none');
|
|
208
|
+
expect(macosPlatform).toContain('id-token: none');
|
|
209
|
+
expect(finalJob).toContain('id-token: write');
|
|
210
|
+
expect(rendered.match(/id-token: write/g)).toHaveLength(1);
|
|
211
|
+
expect(finalJob.indexOf('🧯 Repair pending releases')).toBeLessThan(
|
|
212
|
+
finalJob.indexOf('♻️ Restore validated release state'),
|
|
213
|
+
);
|
|
214
|
+
expect(finalJob.indexOf('♻️ Restore validated release state')).toBeGreaterThan(
|
|
215
|
+
finalJob.indexOf('🧱 Setup Nix/devenv'),
|
|
216
|
+
);
|
|
217
|
+
expect(rendered).not.toContain('GITHUB_SHA:');
|
|
218
|
+
expect(finalJob).toContain("needs.linux-release-candidate.outputs.mode != 'none'");
|
|
219
|
+
expect(finalJob).toContain("inputs.deploy_environment == 'production'");
|
|
220
|
+
expect(finalJob).toContain("inputs.dry_run != 'true'");
|
|
221
|
+
expect(finalJob).toContain('smoo release publish --bump "${{ inputs.bump }}" --dry-run "${{ inputs.dry_run }}"');
|
|
222
|
+
expect(finalJob).toContain('CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}');
|
|
223
|
+
expect(finalJob).toContain('CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}');
|
|
224
|
+
});
|
|
225
|
+
|
|
23
226
|
it('does not wire npm token secrets into repair or publish steps', () => {
|
|
24
227
|
const rendered = renderPublishWorkflowYaml({ repoName: '@smoothbricks/codebase' });
|
|
25
228
|
|
|
@@ -250,11 +453,17 @@ class WorkflowScenarioState {
|
|
|
250
453
|
private readonly repairBuilds = new Set<string>();
|
|
251
454
|
private readonly publishedCurrent = new Set<string>();
|
|
252
455
|
private readonly durableGaps = new Set<string>();
|
|
253
|
-
private readonly validationState
|
|
456
|
+
private readonly validationState: {
|
|
457
|
+
checks: number;
|
|
458
|
+
builds: string[];
|
|
459
|
+
lints: string[];
|
|
460
|
+
tests: string[];
|
|
461
|
+
validates: number;
|
|
462
|
+
} = {
|
|
254
463
|
checks: 0,
|
|
255
|
-
builds: []
|
|
256
|
-
lints: []
|
|
257
|
-
tests: []
|
|
464
|
+
builds: [],
|
|
465
|
+
lints: [],
|
|
466
|
+
tests: [],
|
|
258
467
|
validates: 0,
|
|
259
468
|
};
|
|
260
469
|
|
|
@@ -376,6 +585,26 @@ class WorkflowScenarioState {
|
|
|
376
585
|
}
|
|
377
586
|
}
|
|
378
587
|
|
|
588
|
+
function stepAnchorNumbers(yaml: string): number[] {
|
|
589
|
+
return [...yaml.matchAll(/^\s*# Step (\d+)/gm)].map((match) => Number(match[1]));
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function foldedRunCommand(yaml: string, stepName: string): string {
|
|
593
|
+
const stepStart = yaml.indexOf(`- name: ${stepName}`);
|
|
594
|
+
expect(stepStart).toBeGreaterThanOrEqual(0);
|
|
595
|
+
const runMarker = ' run:\n';
|
|
596
|
+
const runStart = yaml.indexOf(runMarker, stepStart);
|
|
597
|
+
expect(runStart).toBeGreaterThan(stepStart);
|
|
598
|
+
const commandLines: string[] = [];
|
|
599
|
+
for (const line of yaml.slice(runStart + runMarker.length).split('\n')) {
|
|
600
|
+
if (!line.startsWith(' ')) {
|
|
601
|
+
break;
|
|
602
|
+
}
|
|
603
|
+
commandLines.push(line.trim());
|
|
604
|
+
}
|
|
605
|
+
return commandLines.join(' ');
|
|
606
|
+
}
|
|
607
|
+
|
|
379
608
|
function packageNameFromTag(tag: string): string {
|
|
380
609
|
const versionSeparator = tag.lastIndexOf('@');
|
|
381
610
|
if (versionSeparator <= 0) {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* biome-ignore-all lint/suspicious/noTemplateCurlyInString: GitHub Actions expressions are emitted literally. */
|
|
2
|
+
|
|
1
3
|
export enum CiWorkflowStepKind {
|
|
2
4
|
Checkout = 'checkout',
|
|
3
5
|
SetupDevenv = 'setup-devenv',
|
|
@@ -179,7 +181,7 @@ function yamlLinesForStep(step: CiWorkflowStep, options: CiWorkflowDefinitionOpt
|
|
|
179
181
|
' path: |',
|
|
180
182
|
' .nx/cache',
|
|
181
183
|
' .nx/workspace-data/*.db*',
|
|
182
|
-
' key: ${{ runner.os }}-nx-db-v1-${{ github.sha }}',
|
|
184
|
+
' key: ${{ runner.os }}-${{ runner.arch }}-nx-db-v1-${{ github.sha }}',
|
|
183
185
|
];
|
|
184
186
|
case CiWorkflowStepKind.UploadTraceDbs:
|
|
185
187
|
return [
|
|
@@ -1,16 +1,30 @@
|
|
|
1
|
+
/* biome-ignore-all lint/suspicious/noTemplateCurlyInString: GitHub Actions expressions are asserted literally. */
|
|
1
2
|
import { describe, expect, it } from 'bun:test';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { LINUX_PLATFORM_TARGET_GLOBS, PLATFORM_TARGET_GLOBS } from '@smoothbricks/nx-plugin/workspace-config-policy';
|
|
2
6
|
import fc from 'fast-check';
|
|
3
7
|
import {
|
|
4
8
|
extractInlineLocalBlocksForTest,
|
|
5
9
|
INLINE_LOCAL_BEGIN,
|
|
6
10
|
INLINE_LOCAL_END,
|
|
7
11
|
LOCAL_SECTION_MARKER,
|
|
12
|
+
platformTargetGlobsForTest,
|
|
8
13
|
reinsertInlineLocalBlocksForTest,
|
|
9
14
|
splitLocalSectionForTest,
|
|
10
15
|
} from './managed-files.js';
|
|
11
16
|
|
|
12
17
|
const MANAGED = '# managed content\npath merge=driver\n';
|
|
13
18
|
|
|
19
|
+
const REPO_ROOT = join(import.meta.dir, '..', '..', '..', '..');
|
|
20
|
+
const ARCHITECTURE_SCOPED_PREFIX = '${{ runner.os }}-${{ runner.arch }}-';
|
|
21
|
+
const NODE_MODULES_CACHE_KEY = "${{ hashFiles('bun.lock', 'package.json', 'packages/*/package.json') }}";
|
|
22
|
+
const CACHE_ACTIONS = [
|
|
23
|
+
{ name: 'cache-nix-devenv', osKeyLines: 6 },
|
|
24
|
+
{ name: 'cache-node-modules', osKeyLines: 2 },
|
|
25
|
+
{ name: 'cache-nx', osKeyLines: 2 },
|
|
26
|
+
] as const;
|
|
27
|
+
|
|
14
28
|
describe('managed-file local sections', () => {
|
|
15
29
|
it('content without a marker is entirely managed', () => {
|
|
16
30
|
const { managed, localTail } = splitLocalSectionForTest(MANAGED);
|
|
@@ -93,6 +107,19 @@ describe('managed-file inline local blocks', () => {
|
|
|
93
107
|
);
|
|
94
108
|
});
|
|
95
109
|
|
|
110
|
+
it('preserves marker indentation when refreshing nested configuration', () => {
|
|
111
|
+
const markerIndent = ' ';
|
|
112
|
+
const current = [
|
|
113
|
+
'patterns:',
|
|
114
|
+
" - '*.html'",
|
|
115
|
+
`${markerIndent}${INLINE_LOCAL_BEGIN}`,
|
|
116
|
+
" - '!**/templates/*.html'",
|
|
117
|
+
`${markerIndent}${INLINE_LOCAL_END}`,
|
|
118
|
+
].join('\n');
|
|
119
|
+
const { withoutInline, blocks } = extractInlineLocalBlocksForTest(current);
|
|
120
|
+
expect(reinsertInlineLocalBlocksForTest(withoutInline, blocks)).toBe(current);
|
|
121
|
+
});
|
|
122
|
+
|
|
96
123
|
it("reinserting refuses when the anchor no longer appears — never silently drops the repo's customization", () => {
|
|
97
124
|
const fresh = ['a:', ' - one', 'b:'].join('\n'); // ' - two' is gone
|
|
98
125
|
expect(() => reinsertInlineLocalBlocksForTest(fresh, [{ anchor: ' - two', lines: ' - repo-owned' }])).toThrow(
|
|
@@ -126,3 +153,51 @@ describe('managed-file inline local blocks', () => {
|
|
|
126
153
|
);
|
|
127
154
|
});
|
|
128
155
|
});
|
|
156
|
+
|
|
157
|
+
describe('managed publish platform discovery', () => {
|
|
158
|
+
it('returns canonical target families from resolved target names without leaking project names', () => {
|
|
159
|
+
const discovered = platformTargetGlobsForTest(['build', 'bundle-linux', 'package-macos', 'simulator-ios', 'test']);
|
|
160
|
+
|
|
161
|
+
expect(discovered).toEqual([...PLATFORM_TARGET_GLOBS]);
|
|
162
|
+
expect(discovered).not.toContain('native-app');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('selects only supplemental Linux when resolved metadata has no Apple targets', () => {
|
|
166
|
+
expect(platformTargetGlobsForTest(['build', 'bundle-linux', 'test'])).toEqual([...LINUX_PLATFORM_TARGET_GLOBS]);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('returns no platform families for ordinary Nx targets', () => {
|
|
170
|
+
expect(platformTargetGlobsForTest(['build', 'lint', 'test', 'typecheck'])).toEqual([]);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe('managed cache actions', () => {
|
|
175
|
+
it('renders the checked-in action copies from their managed templates', async () => {
|
|
176
|
+
for (const action of CACHE_ACTIONS) {
|
|
177
|
+
const [template, generated] = await Promise.all([
|
|
178
|
+
readFile(
|
|
179
|
+
join(REPO_ROOT, 'packages', 'cli', 'managed', 'templates', 'github', 'actions', action.name, 'action.yml'),
|
|
180
|
+
'utf8',
|
|
181
|
+
),
|
|
182
|
+
readFile(join(REPO_ROOT, '.github', 'actions', action.name, 'action.yml'), 'utf8'),
|
|
183
|
+
]);
|
|
184
|
+
|
|
185
|
+
expect(generated).toBe(template.replace('{{NODE_MODULES_CACHE_KEY}}', NODE_MODULES_CACHE_KEY));
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('scopes every primary, restore, and save key to the runner OS and architecture', async () => {
|
|
190
|
+
for (const action of CACHE_ACTIONS) {
|
|
191
|
+
for (const actionRoot of [
|
|
192
|
+
join(REPO_ROOT, 'packages', 'cli', 'managed', 'templates', 'github', 'actions'),
|
|
193
|
+
join(REPO_ROOT, '.github', 'actions'),
|
|
194
|
+
]) {
|
|
195
|
+
const content = await readFile(join(actionRoot, action.name, 'action.yml'), 'utf8');
|
|
196
|
+
const osKeyLines = content.split('\n').filter((line) => line.includes('${{ runner.os }}'));
|
|
197
|
+
|
|
198
|
+
expect(osKeyLines).toHaveLength(action.osKeyLines);
|
|
199
|
+
expect(osKeyLines.every((line) => line.includes(ARCHITECTURE_SCOPED_PREFIX))).toBe(true);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
});
|
|
@@ -2,6 +2,7 @@ import { execFileSync } from 'node:child_process';
|
|
|
2
2
|
import { existsSync, lstatSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { dirname, join, resolve } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { PLATFORM_TARGET_GLOBS } from '@smoothbricks/nx-plugin/workspace-config-policy';
|
|
5
6
|
import { listReleasePackages, readPackageJson } from '../lib/workspace.js';
|
|
6
7
|
import { renderCiWorkflowYaml } from './ci-workflow.js';
|
|
7
8
|
import { renderPublishWorkflowYaml } from './publish-workflow.js';
|
|
@@ -50,6 +51,7 @@ export const INLINE_LOCAL_END = '# smoo-local-end';
|
|
|
50
51
|
interface InlineLocalBlock {
|
|
51
52
|
anchor: string;
|
|
52
53
|
lines: string;
|
|
54
|
+
markerIndent?: string;
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
/** Pull inline local blocks out of a managed section, returning the section
|
|
@@ -76,7 +78,12 @@ function extractInlineLocalBlocks(managed: string): { withoutInline: string; blo
|
|
|
76
78
|
if (i >= lines.length) {
|
|
77
79
|
throw new Error(`${INLINE_LOCAL_BEGIN} anchored on "${anchor}" has no matching ${INLINE_LOCAL_END}`);
|
|
78
80
|
}
|
|
79
|
-
|
|
81
|
+
const markerIndent = line.slice(0, line.length - line.trimStart().length);
|
|
82
|
+
blocks.push({
|
|
83
|
+
anchor,
|
|
84
|
+
lines: blockLines.join('\n'),
|
|
85
|
+
...(markerIndent === '' ? {} : { markerIndent }),
|
|
86
|
+
});
|
|
80
87
|
i += 1; // skip the END marker line itself
|
|
81
88
|
continue;
|
|
82
89
|
}
|
|
@@ -102,7 +109,14 @@ function reinsertInlineLocalBlocks(content: string, blocks: InlineLocalBlock[]):
|
|
|
102
109
|
'template — reconcile the repo-owned block manually',
|
|
103
110
|
);
|
|
104
111
|
}
|
|
105
|
-
|
|
112
|
+
const markerIndent = block.markerIndent ?? '';
|
|
113
|
+
lines.splice(
|
|
114
|
+
index + 1,
|
|
115
|
+
0,
|
|
116
|
+
`${markerIndent}${INLINE_LOCAL_BEGIN}`,
|
|
117
|
+
...block.lines.split('\n'),
|
|
118
|
+
`${markerIndent}${INLINE_LOCAL_END}`,
|
|
119
|
+
);
|
|
106
120
|
}
|
|
107
121
|
return lines.join('\n');
|
|
108
122
|
}
|
|
@@ -124,6 +138,7 @@ interface ManagedFileContext {
|
|
|
124
138
|
ciPushBranches: string[];
|
|
125
139
|
nodeModulesCacheKey: string;
|
|
126
140
|
repoName: string;
|
|
141
|
+
platformTargetGlobs: string[];
|
|
127
142
|
}
|
|
128
143
|
|
|
129
144
|
interface DeployTargetInfo {
|
|
@@ -282,6 +297,7 @@ function getManagedContent(file: ManagedFile, context: ManagedFileContext): stri
|
|
|
282
297
|
deploy: context.hasProductionDeployTargets,
|
|
283
298
|
deployProvider: context.productionDeployProvider,
|
|
284
299
|
repoName: context.repoName,
|
|
300
|
+
platformTargetGlobs: context.platformTargetGlobs,
|
|
285
301
|
});
|
|
286
302
|
}
|
|
287
303
|
throw new Error(`Unknown generated managed file source ${file.source}`);
|
|
@@ -301,6 +317,7 @@ function getManagedFileContext(root: string): ManagedFileContext {
|
|
|
301
317
|
const ciPushBranches = getCiPushBranches(packageJson?.json);
|
|
302
318
|
const stagingDeploy = getDeployTargetInfo(root, 'staging');
|
|
303
319
|
const productionDeploy = getDeployTargetInfo(root, 'production');
|
|
320
|
+
const platformTargetGlobs = platformTargetGlobsForTest(readResolvedNxTargetNames(root));
|
|
304
321
|
const nodeModulesCacheKey = existsSync(join(root, 'bun.lock'))
|
|
305
322
|
? `$${"{{ hashFiles('bun.lock', 'package.json', 'packages/*/package.json') }}"}`
|
|
306
323
|
: `$${"{{ hashFiles('bun.lockb', 'package.json', 'packages/*/package.json') }}"}`;
|
|
@@ -313,9 +330,46 @@ function getManagedFileContext(root: string): ManagedFileContext {
|
|
|
313
330
|
ciPushBranches,
|
|
314
331
|
nodeModulesCacheKey,
|
|
315
332
|
repoName,
|
|
333
|
+
platformTargetGlobs,
|
|
316
334
|
};
|
|
317
335
|
}
|
|
318
336
|
|
|
337
|
+
export function platformTargetGlobsForTest(targetNames: Iterable<string>): string[] {
|
|
338
|
+
const names = [...targetNames];
|
|
339
|
+
return PLATFORM_TARGET_GLOBS.filter((glob) => {
|
|
340
|
+
const suffix = glob.startsWith('*') ? glob.slice(1) : glob;
|
|
341
|
+
return names.some((name) => name.endsWith(suffix));
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function readResolvedNxTargetNames(root: string): string[] {
|
|
346
|
+
const output = execFileSync('nx', ['show', 'projects', '--json'], {
|
|
347
|
+
cwd: root,
|
|
348
|
+
encoding: 'utf8',
|
|
349
|
+
stdio: ['ignore', 'pipe', 'inherit'],
|
|
350
|
+
});
|
|
351
|
+
const projects: unknown = JSON.parse(output);
|
|
352
|
+
if (!Array.isArray(projects) || !projects.every((project) => typeof project === 'string')) {
|
|
353
|
+
return [];
|
|
354
|
+
}
|
|
355
|
+
const targetNames = new Set<string>();
|
|
356
|
+
for (const project of projects) {
|
|
357
|
+
const projectOutput = execFileSync('nx', ['show', 'project', project, '--json'], {
|
|
358
|
+
cwd: root,
|
|
359
|
+
encoding: 'utf8',
|
|
360
|
+
stdio: ['ignore', 'pipe', 'inherit'],
|
|
361
|
+
});
|
|
362
|
+
const projectJson: unknown = JSON.parse(projectOutput);
|
|
363
|
+
const targets = recordValue(recordValue(projectJson)?.targets);
|
|
364
|
+
if (targets) {
|
|
365
|
+
for (const targetName of Object.keys(targets)) {
|
|
366
|
+
targetNames.add(targetName);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return [...targetNames];
|
|
371
|
+
}
|
|
372
|
+
|
|
319
373
|
function renderTemplate(context: ManagedFileContext, template: string): string {
|
|
320
374
|
return template
|
|
321
375
|
.replaceAll('{{REPO_NAME}}', context.repoName)
|
|
@@ -388,9 +442,11 @@ function readCiPushBranches(packageJson: unknown): string[] {
|
|
|
388
442
|
}
|
|
389
443
|
|
|
390
444
|
function recordValue(value: unknown): Record<string, unknown> | undefined {
|
|
391
|
-
return value
|
|
392
|
-
|
|
393
|
-
|
|
445
|
+
return isRecord(value) ? value : undefined;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
449
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
394
450
|
}
|
|
395
451
|
|
|
396
452
|
function renderYamlFlowList(values: string[]): string {
|
|
@@ -29,7 +29,6 @@ const buildOutputDependencies = [
|
|
|
29
29
|
'*-web',
|
|
30
30
|
'*-html',
|
|
31
31
|
'*-css',
|
|
32
|
-
'*-ios',
|
|
33
32
|
'*-android',
|
|
34
33
|
'*-native',
|
|
35
34
|
'*-napi',
|
|
@@ -70,21 +69,7 @@ describe('root smoo monorepo policy', () => {
|
|
|
70
69
|
expect(rootPackage).toMatchObject({ nx: { includedScripts: [] } });
|
|
71
70
|
expect(nxJson.namedInputs).toEqual(validNamedInputs());
|
|
72
71
|
expect(nxJson.targetDefaults).toEqual(validTargetDefaults());
|
|
73
|
-
expect(nxJson.plugins).toEqual([
|
|
74
|
-
{
|
|
75
|
-
plugin: '@nx/js/typescript',
|
|
76
|
-
options: {
|
|
77
|
-
typecheck: { targetName: 'typecheck' },
|
|
78
|
-
build: {
|
|
79
|
-
targetName: 'tsc-js',
|
|
80
|
-
configName: 'tsconfig.lib.json',
|
|
81
|
-
buildDepsName: 'build-deps',
|
|
82
|
-
watchDepsName: 'watch-deps',
|
|
83
|
-
},
|
|
84
|
-
},
|
|
85
|
-
},
|
|
86
|
-
'@smoothbricks/nx-plugin',
|
|
87
|
-
]);
|
|
72
|
+
expect(nxJson.plugins).toEqual(['@smoothbricks/nx-plugin']);
|
|
88
73
|
expect(validateRootPackagePolicy(root)).toBe(0);
|
|
89
74
|
expect(validateNxReleaseConfig(root)).toBe(0);
|
|
90
75
|
} finally {
|
|
@@ -167,7 +152,7 @@ describe('root smoo monorepo policy', () => {
|
|
|
167
152
|
}
|
|
168
153
|
});
|
|
169
154
|
|
|
170
|
-
it('explains Nx plugin conventions when nx.json is not configured', async () => {
|
|
155
|
+
it('explains SmoothBricks Nx plugin conventions when nx.json is not configured', async () => {
|
|
171
156
|
const root = await mkdtemp(join(tmpdir(), 'smoo-package-policy-'));
|
|
172
157
|
try {
|
|
173
158
|
await writeJson(join(root, 'package.json'), validRootPackage());
|
|
@@ -177,10 +162,8 @@ describe('root smoo monorepo policy', () => {
|
|
|
177
162
|
});
|
|
178
163
|
const errors = captureConsoleErrors();
|
|
179
164
|
|
|
180
|
-
expect(validateNxReleaseConfig(root)).toBe(
|
|
165
|
+
expect(validateNxReleaseConfig(root)).toBe(1);
|
|
181
166
|
|
|
182
|
-
expect(errors.join('\n')).toContain('Official Nx owns TypeScript library inference');
|
|
183
|
-
expect(errors.join('\n')).toContain('tsconfig.lib.json produces tsc-js');
|
|
184
167
|
expect(errors.join('\n')).toContain('Smoo relies on this plugin to infer convention targets');
|
|
185
168
|
expect(errors.join('\n')).not.toContain('Fix:');
|
|
186
169
|
} finally {
|
|
@@ -188,7 +171,7 @@ describe('root smoo monorepo policy', () => {
|
|
|
188
171
|
}
|
|
189
172
|
});
|
|
190
173
|
|
|
191
|
-
it('
|
|
174
|
+
it('rejects the official Nx TypeScript plugin because it bypasses ttsc transforms', async () => {
|
|
192
175
|
const root = await mkdtemp(join(tmpdir(), 'smoo-package-policy-'));
|
|
193
176
|
try {
|
|
194
177
|
await writeJson(join(root, 'package.json'), validRootPackage());
|
|
@@ -206,8 +189,8 @@ describe('root smoo monorepo policy', () => {
|
|
|
206
189
|
|
|
207
190
|
expect(validateNxReleaseConfig(root)).toBe(1);
|
|
208
191
|
|
|
209
|
-
expect(errors.join('\n')).toContain('
|
|
210
|
-
expect(errors.join('\n')).toContain('
|
|
192
|
+
expect(errors.join('\n')).toContain('plugins must not configure @nx/js/typescript');
|
|
193
|
+
expect(errors.join('\n')).toContain('Smoo owns transformer-aware TypeScript targets');
|
|
211
194
|
expect(errors.join('\n')).not.toContain('Fix:');
|
|
212
195
|
} finally {
|
|
213
196
|
await rm(root, { recursive: true, force: true });
|
|
@@ -990,6 +973,56 @@ describe('workspace package script policy', () => {
|
|
|
990
973
|
await rm(root, { recursive: true, force: true });
|
|
991
974
|
}
|
|
992
975
|
});
|
|
976
|
+
it('validates generic CI skip tags against resolved project targets', async () => {
|
|
977
|
+
const root = await createWorkspace({
|
|
978
|
+
rootName: '@fixture/root',
|
|
979
|
+
packages: [
|
|
980
|
+
{
|
|
981
|
+
dir: 'worker',
|
|
982
|
+
name: '@fixture/worker',
|
|
983
|
+
nx: { name: 'worker', tags: ['ci:skip:test', 'ci:skip:test.integration'], targets: {} },
|
|
984
|
+
},
|
|
985
|
+
],
|
|
986
|
+
});
|
|
987
|
+
try {
|
|
988
|
+
const resolvedTargetsByProject = new Map([['worker', new Set(['build', 'test', 'test.integration'])]]);
|
|
989
|
+
expect(validateWorkspaceDependencies(root, { resolvedTargetsByProject })).toBe(0);
|
|
990
|
+
} finally {
|
|
991
|
+
await rm(root, { recursive: true, force: true });
|
|
992
|
+
}
|
|
993
|
+
});
|
|
994
|
+
|
|
995
|
+
it('rejects malformed and missing-target CI skip tags', async () => {
|
|
996
|
+
const root = await createWorkspace({
|
|
997
|
+
rootName: '@fixture/root',
|
|
998
|
+
packages: [
|
|
999
|
+
{
|
|
1000
|
+
dir: 'malformed',
|
|
1001
|
+
name: '@fixture/malformed',
|
|
1002
|
+
nx: { name: 'malformed', tags: ['ci:skip:'], targets: {} },
|
|
1003
|
+
},
|
|
1004
|
+
{
|
|
1005
|
+
dir: 'missing',
|
|
1006
|
+
name: '@fixture/missing',
|
|
1007
|
+
nx: { name: 'missing', tags: ['ci:skip:test'], targets: {} },
|
|
1008
|
+
},
|
|
1009
|
+
],
|
|
1010
|
+
});
|
|
1011
|
+
const errors = captureConsoleErrors();
|
|
1012
|
+
try {
|
|
1013
|
+
const resolvedTargetsByProject = new Map([
|
|
1014
|
+
['malformed', new Set(['test'])],
|
|
1015
|
+
['missing', new Set(['build'])],
|
|
1016
|
+
]);
|
|
1017
|
+
expect(validateWorkspaceDependencies(root, { resolvedTargetsByProject })).toBe(2);
|
|
1018
|
+
expect(errors).toContain(
|
|
1019
|
+
'packages/malformed: nx tag ci:skip: must use ci:skip:<target> with one exact Nx target name',
|
|
1020
|
+
);
|
|
1021
|
+
expect(errors).toContain('packages/missing: nx tag ci:skip:test names missing Nx target test');
|
|
1022
|
+
} finally {
|
|
1023
|
+
await rm(root, { recursive: true, force: true });
|
|
1024
|
+
}
|
|
1025
|
+
});
|
|
993
1026
|
});
|
|
994
1027
|
|
|
995
1028
|
async function createWorkspace(input: {
|
|
@@ -1100,21 +1133,7 @@ function validConfiguredNxJson(): Record<string, unknown> {
|
|
|
1100
1133
|
...validNxJson(),
|
|
1101
1134
|
namedInputs: validNamedInputs(),
|
|
1102
1135
|
targetDefaults: validTargetDefaults(),
|
|
1103
|
-
plugins: [
|
|
1104
|
-
{
|
|
1105
|
-
plugin: '@nx/js/typescript',
|
|
1106
|
-
options: {
|
|
1107
|
-
typecheck: { targetName: 'typecheck' },
|
|
1108
|
-
build: {
|
|
1109
|
-
targetName: 'tsc-js',
|
|
1110
|
-
configName: 'tsconfig.lib.json',
|
|
1111
|
-
buildDepsName: 'build-deps',
|
|
1112
|
-
watchDepsName: 'watch-deps',
|
|
1113
|
-
},
|
|
1114
|
-
},
|
|
1115
|
-
},
|
|
1116
|
-
'@smoothbricks/nx-plugin',
|
|
1117
|
-
],
|
|
1136
|
+
plugins: ['@smoothbricks/nx-plugin'],
|
|
1118
1137
|
};
|
|
1119
1138
|
}
|
|
1120
1139
|
|