@smoothbricks/cli 0.3.4 โ 0.4.0
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/dist/cli.js +14 -0
- package/dist/github-ci/index.d.ts +12 -0
- package/dist/github-ci/index.d.ts.map +1 -1
- package/dist/github-ci/index.js +72 -4
- package/dist/monorepo/ci-workflow.d.ts +26 -0
- package/dist/monorepo/ci-workflow.d.ts.map +1 -0
- package/dist/monorepo/ci-workflow.js +201 -0
- package/dist/monorepo/managed-files.d.ts.map +1 -1
- package/dist/monorepo/managed-files.js +97 -5
- package/dist/monorepo/package-policy.d.ts.map +1 -1
- package/dist/monorepo/package-policy.js +4 -2
- package/dist/monorepo/publish-workflow.d.ts +7 -1
- package/dist/monorepo/publish-workflow.d.ts.map +1 -1
- package/dist/monorepo/publish-workflow.js +59 -15
- package/managed/raw/git-format-staged.yml +4 -0
- package/package.json +2 -2
- package/src/cli.ts +35 -5
- package/src/github-ci/index.ts +94 -8
- package/src/monorepo/__tests__/ci-workflow.test.ts +34 -0
- package/src/monorepo/__tests__/publish-workflow.test.ts +65 -2
- package/src/monorepo/ci-workflow.ts +229 -0
- package/src/monorepo/managed-files.ts +115 -5
- package/src/monorepo/package-policy.test.ts +16 -6
- package/src/monorepo/package-policy.ts +4 -2
- package/src/monorepo/publish-workflow.ts +73 -16
- package/managed/templates/github/workflows/ci.yml +0 -102
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
export enum CiWorkflowStepKind {
|
|
2
|
+
Checkout = 'checkout',
|
|
3
|
+
SetupDevenv = 'setup-devenv',
|
|
4
|
+
SetNxShas = 'set-nx-shas',
|
|
5
|
+
RestoreNxCache = 'restore-nx-cache',
|
|
6
|
+
Build = 'build',
|
|
7
|
+
Lint = 'lint',
|
|
8
|
+
UnitTests = 'unit-tests',
|
|
9
|
+
Deploy = 'deploy',
|
|
10
|
+
SaveNxCache = 'save-nx-cache',
|
|
11
|
+
UploadTraceDbs = 'upload-trace-dbs',
|
|
12
|
+
SaveNixDevenv = 'save-nix-devenv',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface CiWorkflowStep {
|
|
16
|
+
kind: CiWorkflowStepKind;
|
|
17
|
+
name: string;
|
|
18
|
+
number: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface CiWorkflowDefinitionOptions {
|
|
22
|
+
deploy: boolean;
|
|
23
|
+
deployProvider?: 'cloudflare';
|
|
24
|
+
pushBranches: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type CiWorkflowStepInput = Omit<CiWorkflowStep, 'number'>;
|
|
28
|
+
|
|
29
|
+
export function defineCiWorkflow(options: CiWorkflowDefinitionOptions): CiWorkflowStep[] {
|
|
30
|
+
const steps: CiWorkflowStepInput[] = [
|
|
31
|
+
{ kind: CiWorkflowStepKind.Checkout, name: '๐ฅ Checkout' },
|
|
32
|
+
{ kind: CiWorkflowStepKind.SetupDevenv, name: '๐งฑ Setup Nix/devenv' },
|
|
33
|
+
{ kind: CiWorkflowStepKind.SetNxShas, name: '๐งญ Set Nx SHAs' },
|
|
34
|
+
{ kind: CiWorkflowStepKind.RestoreNxCache, name: '๐ง Restore Nx cache' },
|
|
35
|
+
{ kind: CiWorkflowStepKind.Build, name: '๐จ Build' },
|
|
36
|
+
{ kind: CiWorkflowStepKind.Lint, name: '๐ Lint' },
|
|
37
|
+
{ kind: CiWorkflowStepKind.UnitTests, name: '๐งช Unit Tests' },
|
|
38
|
+
];
|
|
39
|
+
if (options.deploy) {
|
|
40
|
+
steps.push({ kind: CiWorkflowStepKind.Deploy, name: '๐ Deploy Staging' });
|
|
41
|
+
}
|
|
42
|
+
steps.push(
|
|
43
|
+
{ kind: CiWorkflowStepKind.SaveNxCache, name: '๐พ Save Nx cache' },
|
|
44
|
+
{ kind: CiWorkflowStepKind.UploadTraceDbs, name: '๐ Upload trace DBs' },
|
|
45
|
+
{ kind: CiWorkflowStepKind.SaveNixDevenv, name: '๐งน Cleanup and cache Nix/devenv' },
|
|
46
|
+
);
|
|
47
|
+
return steps.map((step, index) => ({ ...step, number: index + 2 }));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function renderCiWorkflowYaml(options: CiWorkflowDefinitionOptions): string {
|
|
51
|
+
const steps = defineCiWorkflow(options);
|
|
52
|
+
return `${renderCiWorkflowHeader(options)}${renderCiWorkflowSteps(steps, options)}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function renderCiWorkflowHeader(options: CiWorkflowDefinitionOptions): string {
|
|
56
|
+
return `name: CI
|
|
57
|
+
|
|
58
|
+
on:
|
|
59
|
+
push:
|
|
60
|
+
branches:
|
|
61
|
+
${renderYamlList(options.pushBranches, 6)}
|
|
62
|
+
pull_request:
|
|
63
|
+
|
|
64
|
+
permissions:
|
|
65
|
+
actions: read
|
|
66
|
+
contents: read
|
|
67
|
+
statuses: write
|
|
68
|
+
|
|
69
|
+
defaults:
|
|
70
|
+
run:
|
|
71
|
+
working-directory: tooling/direnv
|
|
72
|
+
|
|
73
|
+
jobs:
|
|
74
|
+
main:
|
|
75
|
+
name: Validate
|
|
76
|
+
runs-on: ubuntu-latest
|
|
77
|
+
timeout-minutes: 45
|
|
78
|
+
env:
|
|
79
|
+
NIX_STORE_NAR: ${githubExpression('github.workspace')}/nix-store.nar
|
|
80
|
+
GH_TOKEN: ${githubExpression('github.token')}
|
|
81
|
+
steps:
|
|
82
|
+
`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function githubExpression(expression: string): string {
|
|
86
|
+
return `$${`{{ ${expression} }}`}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function renderCiWorkflowSteps(steps: CiWorkflowStep[], options: CiWorkflowDefinitionOptions): string {
|
|
90
|
+
const lines: string[] = [];
|
|
91
|
+
for (const step of steps) {
|
|
92
|
+
lines.push(...sectionLinesBefore(step));
|
|
93
|
+
lines.push(...commentLinesForStep(step));
|
|
94
|
+
lines.push(...yamlLinesForStep(step, options));
|
|
95
|
+
lines.push('');
|
|
96
|
+
}
|
|
97
|
+
return `${lines.join('\n').trimEnd()}\n`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function sectionLinesBefore(step: CiWorkflowStep): string[] {
|
|
101
|
+
if (step.kind === CiWorkflowStepKind.SetNxShas) {
|
|
102
|
+
return [' # --- Nx -----------------------------------------------------------------', ''];
|
|
103
|
+
}
|
|
104
|
+
if (step.kind === CiWorkflowStepKind.SaveNixDevenv) {
|
|
105
|
+
return [' # --- Cleanup ------------------------------------------------------------', ''];
|
|
106
|
+
}
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function commentLinesForStep(step: CiWorkflowStep): string[] {
|
|
111
|
+
if (step.kind === CiWorkflowStepKind.Checkout) {
|
|
112
|
+
return [' # Step 1: GitHub adds "Set up job" automatically', ' # Step 2'];
|
|
113
|
+
}
|
|
114
|
+
if (step.kind === CiWorkflowStepKind.SetupDevenv) {
|
|
115
|
+
return [
|
|
116
|
+
' # Step 3. Composite action internals do not affect top-level job step',
|
|
117
|
+
' # anchors; update the nx-smart --step values below if top-level steps move.',
|
|
118
|
+
];
|
|
119
|
+
}
|
|
120
|
+
if (step.kind === CiWorkflowStepKind.SetNxShas) {
|
|
121
|
+
return [` # Step ${step.number}`, ' # Sets the base and head SHAs required for the nx affected commands'];
|
|
122
|
+
}
|
|
123
|
+
if (step.kind === CiWorkflowStepKind.SaveNxCache) {
|
|
124
|
+
return [
|
|
125
|
+
` # Step ${step.number}`,
|
|
126
|
+
" # Nx's database cache needs artifact files and .nx/workspace-data DB",
|
|
127
|
+
' # metadata restored together; GitHub Actions cache is only the archive',
|
|
128
|
+
' # transport. Save runs only after prior required steps succeed on the default',
|
|
129
|
+
' # branch, so PRs may restore shared cache but cannot publish it.',
|
|
130
|
+
];
|
|
131
|
+
}
|
|
132
|
+
return [` # Step ${step.number}`];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function yamlLinesForStep(step: CiWorkflowStep, options: CiWorkflowDefinitionOptions): string[] {
|
|
136
|
+
switch (step.kind) {
|
|
137
|
+
case CiWorkflowStepKind.Checkout:
|
|
138
|
+
return [
|
|
139
|
+
` - name: ${step.name}`,
|
|
140
|
+
' uses: actions/checkout@v6.0.2',
|
|
141
|
+
' with:',
|
|
142
|
+
' filter: blob:none',
|
|
143
|
+
' fetch-depth: 0',
|
|
144
|
+
];
|
|
145
|
+
case CiWorkflowStepKind.SetupDevenv:
|
|
146
|
+
return [` - name: ${step.name}`, ' id: setup', ' uses: ./.github/actions/setup-devenv'];
|
|
147
|
+
case CiWorkflowStepKind.SetNxShas:
|
|
148
|
+
return [
|
|
149
|
+
` - name: ${step.name}`,
|
|
150
|
+
' uses: nrwl/nx-set-shas@v5.0.1',
|
|
151
|
+
' with:',
|
|
152
|
+
' workflow-id: ci.yml',
|
|
153
|
+
];
|
|
154
|
+
case CiWorkflowStepKind.RestoreNxCache:
|
|
155
|
+
return [` - name: ${step.name}`, ' id: nx-cache', ' uses: ./.github/actions/cache-nx'];
|
|
156
|
+
case CiWorkflowStepKind.Build:
|
|
157
|
+
return nxSmartStep(step, 'build', 'Build');
|
|
158
|
+
case CiWorkflowStepKind.Lint:
|
|
159
|
+
return nxSmartStep(step, 'lint', 'Lint');
|
|
160
|
+
case CiWorkflowStepKind.UnitTests:
|
|
161
|
+
return nxSmartStep(step, 'test', 'Unit Tests');
|
|
162
|
+
case CiWorkflowStepKind.Deploy:
|
|
163
|
+
return [
|
|
164
|
+
` - name: ${step.name}`,
|
|
165
|
+
' if:',
|
|
166
|
+
" ${{ github.event_name == 'push' && github.ref == format('refs/heads/{0}',",
|
|
167
|
+
' github.event.repository.default_branch) }}',
|
|
168
|
+
...deployEnvLines(options),
|
|
169
|
+
` run: smoo github-ci nx-deploy --configuration staging --mode affected --name "Deploy Staging" --step ${step.number}`,
|
|
170
|
+
];
|
|
171
|
+
case CiWorkflowStepKind.SaveNxCache:
|
|
172
|
+
return [
|
|
173
|
+
` - name: ${step.name}`,
|
|
174
|
+
' if:',
|
|
175
|
+
" ${{ github.event_name == 'push' && github.ref == format('refs/heads/{0}',",
|
|
176
|
+
" github.event.repository.default_branch) && steps.nx-cache.outputs.cache-hit != 'true' }}",
|
|
177
|
+
' uses: actions/cache/save@v5.0.5',
|
|
178
|
+
' with:',
|
|
179
|
+
' path: |',
|
|
180
|
+
' .nx/cache',
|
|
181
|
+
' .nx/workspace-data/*.db*',
|
|
182
|
+
' key: ${{ runner.os }}-nx-db-v1-${{ github.sha }}',
|
|
183
|
+
];
|
|
184
|
+
case CiWorkflowStepKind.UploadTraceDbs:
|
|
185
|
+
return [
|
|
186
|
+
` - name: ${step.name}`,
|
|
187
|
+
' if: ${{ always() }}',
|
|
188
|
+
' uses: actions/upload-artifact@v7.0.1',
|
|
189
|
+
' with:',
|
|
190
|
+
' name: trace-results-${{ github.run_id }}',
|
|
191
|
+
' path: packages/*/.trace-results.db',
|
|
192
|
+
' if-no-files-found: ignore',
|
|
193
|
+
' retention-days: 14',
|
|
194
|
+
' include-hidden-files: true',
|
|
195
|
+
];
|
|
196
|
+
case CiWorkflowStepKind.SaveNixDevenv:
|
|
197
|
+
return [
|
|
198
|
+
` - name: ${step.name}`,
|
|
199
|
+
' if: ${{ always() }}',
|
|
200
|
+
' uses: ./.github/actions/save-nix-devenv',
|
|
201
|
+
' with:',
|
|
202
|
+
' nix-cache-hit: ${{ steps.setup.outputs.nix-cache-hit }}',
|
|
203
|
+
' devenv-cache-hit: ${{ steps.setup.outputs.devenv-cache-hit }}',
|
|
204
|
+
];
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function deployEnvLines(options: CiWorkflowDefinitionOptions): string[] {
|
|
209
|
+
if (options.deployProvider !== 'cloudflare') {
|
|
210
|
+
return [];
|
|
211
|
+
}
|
|
212
|
+
return [
|
|
213
|
+
' env:',
|
|
214
|
+
' CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}',
|
|
215
|
+
' CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}',
|
|
216
|
+
];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function nxSmartStep(step: CiWorkflowStep, target: string, name: string): string[] {
|
|
220
|
+
return [
|
|
221
|
+
` - name: ${step.name}`,
|
|
222
|
+
` run: smoo github-ci nx-smart --target ${target} --name "${name}" --step ${step.number}`,
|
|
223
|
+
];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function renderYamlList(values: string[], spaces: number): string {
|
|
227
|
+
const indent = ' '.repeat(spaces);
|
|
228
|
+
return values.map((value) => `${indent}- ${value}`).join('\n');
|
|
229
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
1
2
|
import { existsSync, lstatSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
3
|
import { dirname, join, resolve } from 'node:path';
|
|
3
4
|
import { fileURLToPath } from 'node:url';
|
|
4
5
|
import { listReleasePackages, readPackageJson } from '../lib/workspace.js';
|
|
6
|
+
import { renderCiWorkflowYaml } from './ci-workflow.js';
|
|
5
7
|
import { renderPublishWorkflowYaml } from './publish-workflow.js';
|
|
6
8
|
|
|
7
9
|
type ManagedKind = 'raw' | 'template' | 'generated';
|
|
@@ -21,10 +23,20 @@ export interface FileResult {
|
|
|
21
23
|
|
|
22
24
|
interface ManagedFileContext {
|
|
23
25
|
hasReleasePackages: boolean;
|
|
26
|
+
hasStagingDeployTargets: boolean;
|
|
27
|
+
hasProductionDeployTargets: boolean;
|
|
28
|
+
stagingDeployProvider?: 'cloudflare';
|
|
29
|
+
productionDeployProvider?: 'cloudflare';
|
|
30
|
+
ciPushBranches: string[];
|
|
24
31
|
nodeModulesCacheKey: string;
|
|
25
32
|
repoName: string;
|
|
26
33
|
}
|
|
27
34
|
|
|
35
|
+
interface DeployTargetInfo {
|
|
36
|
+
exists: boolean;
|
|
37
|
+
provider?: 'cloudflare';
|
|
38
|
+
}
|
|
39
|
+
|
|
28
40
|
const managedFiles: ManagedFile[] = [
|
|
29
41
|
{
|
|
30
42
|
kind: 'raw',
|
|
@@ -56,8 +68,8 @@ const managedFiles: ManagedFile[] = [
|
|
|
56
68
|
target: '.git-format-staged.yml',
|
|
57
69
|
},
|
|
58
70
|
{
|
|
59
|
-
kind: '
|
|
60
|
-
source: '
|
|
71
|
+
kind: 'generated',
|
|
72
|
+
source: 'ci-workflow',
|
|
61
73
|
target: '.github/workflows/ci.yml',
|
|
62
74
|
},
|
|
63
75
|
{
|
|
@@ -106,7 +118,7 @@ function applyManagedFile(
|
|
|
106
118
|
mode: 'update' | 'check' | 'diff',
|
|
107
119
|
context: ManagedFileContext,
|
|
108
120
|
): FileResult {
|
|
109
|
-
if (file.releasePackagesOnly === true && !context.hasReleasePackages) {
|
|
121
|
+
if (file.releasePackagesOnly === true && !context.hasReleasePackages && !context.hasProductionDeployTargets) {
|
|
110
122
|
return { target: file.target, action: 'skipped' };
|
|
111
123
|
}
|
|
112
124
|
const target = resolve(root, file.target);
|
|
@@ -138,8 +150,19 @@ function applyManagedFile(
|
|
|
138
150
|
|
|
139
151
|
function getManagedContent(file: ManagedFile, context: ManagedFileContext): string {
|
|
140
152
|
if (file.kind === 'generated') {
|
|
153
|
+
if (file.source === 'ci-workflow') {
|
|
154
|
+
return renderCiWorkflowYaml({
|
|
155
|
+
deploy: context.hasStagingDeployTargets,
|
|
156
|
+
deployProvider: context.stagingDeployProvider,
|
|
157
|
+
pushBranches: context.ciPushBranches,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
141
160
|
if (file.source === 'publish-workflow') {
|
|
142
|
-
return renderPublishWorkflowYaml({
|
|
161
|
+
return renderPublishWorkflowYaml({
|
|
162
|
+
deploy: context.hasProductionDeployTargets,
|
|
163
|
+
deployProvider: context.productionDeployProvider,
|
|
164
|
+
repoName: context.repoName,
|
|
165
|
+
});
|
|
143
166
|
}
|
|
144
167
|
throw new Error(`Unknown generated managed file source ${file.source}`);
|
|
145
168
|
}
|
|
@@ -155,18 +178,105 @@ function getManagedContent(file: ManagedFile, context: ManagedFileContext): stri
|
|
|
155
178
|
function getManagedFileContext(root: string): ManagedFileContext {
|
|
156
179
|
const packageJson = readPackageJson(join(root, 'package.json'));
|
|
157
180
|
const repoName = packageJson?.name ?? 'monorepo';
|
|
181
|
+
const ciPushBranches = getCiPushBranches(packageJson?.json);
|
|
182
|
+
const stagingDeploy = getDeployTargetInfo(root, 'staging');
|
|
183
|
+
const productionDeploy = getDeployTargetInfo(root, 'production');
|
|
158
184
|
const nodeModulesCacheKey = existsSync(join(root, 'bun.lock'))
|
|
159
185
|
? `$${"{{ hashFiles('bun.lock', 'package.json', 'packages/*/package.json') }}"}`
|
|
160
186
|
: `$${"{{ hashFiles('bun.lockb', 'package.json', 'packages/*/package.json') }}"}`;
|
|
161
|
-
return {
|
|
187
|
+
return {
|
|
188
|
+
hasReleasePackages: listReleasePackages(root, packageJson).length > 0,
|
|
189
|
+
hasStagingDeployTargets: stagingDeploy.exists,
|
|
190
|
+
hasProductionDeployTargets: productionDeploy.exists,
|
|
191
|
+
stagingDeployProvider: stagingDeploy.provider,
|
|
192
|
+
productionDeployProvider: productionDeploy.provider,
|
|
193
|
+
ciPushBranches,
|
|
194
|
+
nodeModulesCacheKey,
|
|
195
|
+
repoName,
|
|
196
|
+
};
|
|
162
197
|
}
|
|
163
198
|
|
|
164
199
|
function renderTemplate(context: ManagedFileContext, template: string): string {
|
|
165
200
|
return template
|
|
166
201
|
.replaceAll('{{REPO_NAME}}', context.repoName)
|
|
202
|
+
.replaceAll('__SMOO_CI_PUSH_BRANCHES__', renderYamlFlowList(context.ciPushBranches))
|
|
167
203
|
.replaceAll('{{NODE_MODULES_CACHE_KEY}}', context.nodeModulesCacheKey);
|
|
168
204
|
}
|
|
169
205
|
|
|
206
|
+
function getDeployTargetInfo(root: string, configuration: string): DeployTargetInfo {
|
|
207
|
+
const output = execFileSync('nx', ['show', 'projects', '--withTarget', 'deploy', '--json'], {
|
|
208
|
+
cwd: root,
|
|
209
|
+
encoding: 'utf8',
|
|
210
|
+
stdio: ['ignore', 'pipe', 'inherit'],
|
|
211
|
+
});
|
|
212
|
+
const projects: unknown = JSON.parse(output);
|
|
213
|
+
if (!Array.isArray(projects) || !projects.every((project) => typeof project === 'string')) {
|
|
214
|
+
return { exists: false };
|
|
215
|
+
}
|
|
216
|
+
let exists = false;
|
|
217
|
+
let provider: DeployTargetInfo['provider'];
|
|
218
|
+
for (const project of projects) {
|
|
219
|
+
const target = nxDeployTarget(root, project, configuration);
|
|
220
|
+
if (!target.exists) {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
exists = true;
|
|
224
|
+
provider ??= target.provider;
|
|
225
|
+
}
|
|
226
|
+
return { exists, provider };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function nxDeployTarget(root: string, project: string, configuration: string): DeployTargetInfo {
|
|
230
|
+
const output = execFileSync('nx', ['show', 'project', project, '--json'], {
|
|
231
|
+
cwd: root,
|
|
232
|
+
encoding: 'utf8',
|
|
233
|
+
stdio: ['ignore', 'pipe', 'inherit'],
|
|
234
|
+
});
|
|
235
|
+
const parsed: unknown = JSON.parse(output);
|
|
236
|
+
const projectJson = recordValue(parsed);
|
|
237
|
+
const targets = recordValue(projectJson?.targets);
|
|
238
|
+
const deploy = recordValue(targets?.deploy);
|
|
239
|
+
const configurations = recordValue(deploy?.configurations);
|
|
240
|
+
const config = recordValue(configurations?.[configuration]);
|
|
241
|
+
if (!config) {
|
|
242
|
+
return { exists: false };
|
|
243
|
+
}
|
|
244
|
+
const command = deployCommand(deploy, config);
|
|
245
|
+
return { exists: true, provider: command.includes('wrangler ') ? 'cloudflare' : undefined };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function deployCommand(deploy: Record<string, unknown> | undefined, config: Record<string, unknown>): string {
|
|
249
|
+
const command = stringValue(config.command) ?? stringValue(recordValue(config.options)?.command);
|
|
250
|
+
return command ?? stringValue(recordValue(deploy?.options)?.command) ?? '';
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function stringValue(value: unknown): string | undefined {
|
|
254
|
+
return typeof value === 'string' ? value : undefined;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function getCiPushBranches(packageJson: unknown): string[] {
|
|
258
|
+
const configured = readCiPushBranches(packageJson);
|
|
259
|
+
return configured.length > 0 ? configured : ['main'];
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function readCiPushBranches(packageJson: unknown): string[] {
|
|
263
|
+
const rootPackage = recordValue(packageJson);
|
|
264
|
+
const smoo = recordValue(rootPackage?.smoo);
|
|
265
|
+
const github = recordValue(smoo?.github);
|
|
266
|
+
const branches = Array.isArray(github?.pushBranches) ? github.pushBranches : [];
|
|
267
|
+
return branches.filter((branch): branch is string => typeof branch === 'string' && branch.length > 0);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function recordValue(value: unknown): Record<string, unknown> | undefined {
|
|
271
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
272
|
+
? (value as Record<string, unknown>)
|
|
273
|
+
: undefined;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function renderYamlFlowList(values: string[]): string {
|
|
277
|
+
return JSON.stringify(values);
|
|
278
|
+
}
|
|
279
|
+
|
|
170
280
|
function writeManagedFile(path: string, content: string, executable: boolean): void {
|
|
171
281
|
mkdirSync(dirname(path), { recursive: true });
|
|
172
282
|
writeFileSync(path, content, { mode: executable ? 0o755 : 0o644 });
|
|
@@ -51,14 +51,17 @@ describe('root smoo monorepo policy', () => {
|
|
|
51
51
|
);
|
|
52
52
|
await writeJson(join(root, 'nx.json'), validNxJson());
|
|
53
53
|
|
|
54
|
-
expect(validateRootPackagePolicy(root)).toBe(
|
|
55
|
-
expect(validateNxReleaseConfig(root)).toBe(
|
|
54
|
+
expect(validateRootPackagePolicy(root)).toBe(7);
|
|
55
|
+
expect(validateNxReleaseConfig(root)).toBe(8);
|
|
56
56
|
|
|
57
57
|
applyFixableMonorepoDefaults(root);
|
|
58
58
|
|
|
59
59
|
const rootPackage = await readJson(join(root, 'package.json'));
|
|
60
60
|
const nxJson = await readJson(join(root, 'nx.json'));
|
|
61
61
|
expect(rootPackage.scripts).toEqual({
|
|
62
|
+
clean: 'nx run-many -t clean; nx reset',
|
|
63
|
+
'clean:node_modules':
|
|
64
|
+
'rm -rf node_modules && find e* t* p* -type d -name node_modules -print0 | xargs -0 rm -rvf',
|
|
62
65
|
'format:changed': 'git-format-staged --config tooling/git-hooks/git-format-staged.yml --also-unstaged',
|
|
63
66
|
'format:staged': 'git-format-staged --config tooling/git-hooks/git-format-staged.yml',
|
|
64
67
|
lint: 'nx run-many -t lint',
|
|
@@ -66,7 +69,7 @@ describe('root smoo monorepo policy', () => {
|
|
|
66
69
|
});
|
|
67
70
|
expect(rootPackage).toMatchObject({ nx: { includedScripts: [] } });
|
|
68
71
|
expect(nxJson.namedInputs).toEqual(validNamedInputs());
|
|
69
|
-
expect(nxJson.targetDefaults).toEqual(
|
|
72
|
+
expect(nxJson.targetDefaults).toEqual(validTargetDefaults());
|
|
70
73
|
expect(nxJson.plugins).toEqual([
|
|
71
74
|
{
|
|
72
75
|
plugin: '@nx/js/typescript',
|
|
@@ -152,12 +155,12 @@ describe('root smoo monorepo policy', () => {
|
|
|
152
155
|
},
|
|
153
156
|
});
|
|
154
157
|
|
|
155
|
-
expect(validateNxReleaseConfig(root)).toBe(
|
|
158
|
+
expect(validateNxReleaseConfig(root)).toBe(6);
|
|
156
159
|
|
|
157
160
|
applyFixableMonorepoDefaults(root);
|
|
158
161
|
|
|
159
162
|
const nxJson = await readJson(join(root, 'nx.json'));
|
|
160
|
-
expect(nxJson.targetDefaults).toEqual(
|
|
163
|
+
expect(nxJson.targetDefaults).toEqual(validTargetDefaults());
|
|
161
164
|
expect(validateNxReleaseConfig(root)).toBe(0);
|
|
162
165
|
} finally {
|
|
163
166
|
await rm(root, { recursive: true, force: true });
|
|
@@ -1096,7 +1099,7 @@ function validConfiguredNxJson(): Record<string, unknown> {
|
|
|
1096
1099
|
return {
|
|
1097
1100
|
...validNxJson(),
|
|
1098
1101
|
namedInputs: validNamedInputs(),
|
|
1099
|
-
targetDefaults:
|
|
1102
|
+
targetDefaults: validTargetDefaults(),
|
|
1100
1103
|
plugins: [
|
|
1101
1104
|
{
|
|
1102
1105
|
plugin: '@nx/js/typescript',
|
|
@@ -1115,6 +1118,13 @@ function validConfiguredNxJson(): Record<string, unknown> {
|
|
|
1115
1118
|
};
|
|
1116
1119
|
}
|
|
1117
1120
|
|
|
1121
|
+
function validTargetDefaults(): Record<string, unknown> {
|
|
1122
|
+
return {
|
|
1123
|
+
build: { cache: true, outputs: ['{projectRoot}/dist'] },
|
|
1124
|
+
clean: { executor: '@smoothbricks/nx-plugin:clean-outputs', cache: false },
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1118
1128
|
function validNamedInputs(): Record<string, unknown> {
|
|
1119
1129
|
return {
|
|
1120
1130
|
default: ['{projectRoot}/**/*', 'sharedGlobals'],
|
|
@@ -48,10 +48,12 @@ export { SMOO_NX_RELEASE_TAG_PATTERN, SMOO_NX_VERSION_ACTIONS };
|
|
|
48
48
|
|
|
49
49
|
const extraCommitScopes = ['release'];
|
|
50
50
|
const rootScriptPolicy: Record<string, string> = {
|
|
51
|
+
clean: 'nx run-many -t clean; nx reset',
|
|
52
|
+
'clean:node_modules': 'rm -rf node_modules && find e* t* p* -type d -name node_modules -print0 | xargs -0 rm -rvf',
|
|
53
|
+
'format:changed': 'git-format-staged --config tooling/git-hooks/git-format-staged.yml --also-unstaged',
|
|
54
|
+
'format:staged': 'git-format-staged --config tooling/git-hooks/git-format-staged.yml',
|
|
51
55
|
lint: 'nx run-many -t lint',
|
|
52
56
|
'lint:fix': 'git-format-staged --config tooling/git-hooks/git-format-staged.yml --unstaged',
|
|
53
|
-
'format:staged': 'git-format-staged --config tooling/git-hooks/git-format-staged.yml',
|
|
54
|
-
'format:changed': 'git-format-staged --config tooling/git-hooks/git-format-staged.yml --also-unstaged',
|
|
55
57
|
};
|
|
56
58
|
|
|
57
59
|
export function applyFixableMonorepoDefaults(root: string): void {
|