@mettlecast/domain-cli 0.2.59 → 0.2.60

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.
Files changed (49) hide show
  1. package/dist/builder/build-registry.js +128 -13
  2. package/dist/builder/load-module.js +3 -3
  3. package/dist/cli.js +2 -2
  4. package/dist/commands/build-catalog.d.ts +2 -2
  5. package/dist/commands/build-catalog.js +5 -5
  6. package/dist/commands/build-flows.js +1 -1
  7. package/dist/commands/build.js +5 -5
  8. package/dist/commands/check-hashes.js +2 -2
  9. package/dist/commands/create-project.js +1 -1
  10. package/dist/commands/doctor.d.ts +5 -7
  11. package/dist/commands/doctor.js +44 -165
  12. package/dist/commands/power-tune.js +2 -2
  13. package/dist/commands/show-dns.d.ts +1 -1
  14. package/dist/commands/show-dns.js +5 -5
  15. package/dist/commands/upgrade.js +17 -11
  16. package/dist/commands/validate.js +183 -0
  17. package/dist/utils/header-inject.js +2 -2
  18. package/dist/utils/install-file.d.ts +1 -1
  19. package/dist/utils/install-file.js +1 -1
  20. package/dist/utils/manifest.js +1 -2
  21. package/dist/utils/scaffold-config.d.ts +2 -2
  22. package/dist/utils/scaffold-config.js +1 -1
  23. package/package.json +1 -1
  24. package/src/__tests__/commands/check-hashes.test.ts +9 -9
  25. package/src/__tests__/commands/upgrade.test.ts +7 -7
  26. package/src/__tests__/doctor.test.ts +60 -67
  27. package/src/__tests__/package-freshness.test.ts +114 -0
  28. package/src/__tests__/scaffold-src/part-a-layout.test.ts +10 -10
  29. package/src/__tests__/scripts/package-scaffold.test.ts +5 -5
  30. package/src/__tests__/utils/install-file.test.ts +2 -2
  31. package/src/__tests__/utils/manifest.test.ts +2 -2
  32. package/src/__tests__/validate.test.ts +652 -1
  33. package/src/builder/build-registry.ts +147 -15
  34. package/src/builder/load-module.ts +3 -3
  35. package/src/cli.ts +3 -3
  36. package/src/commands/build-catalog.ts +5 -5
  37. package/src/commands/build-flows.ts +1 -1
  38. package/src/commands/build.ts +5 -5
  39. package/src/commands/check-hashes.ts +2 -2
  40. package/src/commands/create-project.ts +1 -1
  41. package/src/commands/doctor.ts +52 -181
  42. package/src/commands/power-tune.ts +2 -2
  43. package/src/commands/show-dns.ts +5 -5
  44. package/src/commands/upgrade.ts +16 -10
  45. package/src/commands/validate.ts +226 -1
  46. package/src/utils/header-inject.ts +2 -2
  47. package/src/utils/install-file.ts +1 -1
  48. package/src/utils/manifest.ts +1 -2
  49. package/src/utils/scaffold-config.ts +3 -3
@@ -26,7 +26,7 @@ function toZoneLine(r: DnsRecord): string {
26
26
  }
27
27
 
28
28
  /**
29
- * tib show-dns [--env dev|staging|prod] [--project-dir <path>]
29
+ * mc-domain-module show-dns [--env dev|staging|prod] [--project-dir <path>]
30
30
  *
31
31
  * Reads the scaffold-config.json and prints DNS records that must be added for
32
32
  * custom domain wiring: certificate validation CNAMEs (step 1) and traffic
@@ -37,27 +37,27 @@ function toZoneLine(r: DnsRecord): string {
37
37
  */
38
38
  export async function runShowDns(opts: ShowDnsOptions): Promise<void> {
39
39
  const projectDir = opts.projectDir ? path.resolve(opts.projectDir) : process.cwd();
40
- const configPath = path.join(projectDir, '.tib', 'scaffold-config.json');
40
+ const configPath = path.join(projectDir, '.mc', 'scaffold-config.json');
41
41
 
42
42
  let config: { environments?: Record<string, { dashboardUrl?: string }> };
43
43
  try {
44
44
  const raw = await readFile(configPath, 'utf-8');
45
45
  config = JSON.parse(raw) as typeof config;
46
46
  } catch {
47
- console.error('[tib] Could not read .mc/scaffold-config.json — run from a TIB project root.');
47
+ console.error('[mc-domain-module] Could not read .mc/scaffold-config.json — run from a Mettlecast project root.');
48
48
  process.exit(1);
49
49
  }
50
50
 
51
51
  const environments = config.environments ?? {};
52
52
  if (Object.keys(environments).length === 0) {
53
- console.log('[tib] No environments configured. Set a custom domain via `tib show-dns` or the Setup tab.');
53
+ console.log('[mc-domain-module] No environments configured. Set a custom domain via `npx mc-domain-module show-dns` or the Setup tab.');
54
54
  return;
55
55
  }
56
56
 
57
57
  // Try to load CDK outputs for routing records
58
58
  let cdkOutputs: Record<string, Record<string, string>> = {};
59
59
  try {
60
- const outputsPath = path.join(projectDir, '.tib', 'cdk-outputs.json');
60
+ const outputsPath = path.join(projectDir, '.mc', 'cdk-outputs.json');
61
61
  const raw = await readFile(outputsPath, 'utf-8');
62
62
  cdkOutputs = JSON.parse(raw) as typeof cdkOutputs;
63
63
  } catch {
@@ -251,9 +251,9 @@ function buildPrBody(
251
251
  lines.push('### Drift warnings — manual review required');
252
252
  lines.push(
253
253
  'The following files were locally modified after scaffold installation. ' +
254
- 'New versions have been written to `{path}.tib-upgrade` — merge manually then remove the `.tib-upgrade` file.'
254
+ 'New versions have been written to `{path}.mc-upgrade` — merge manually then remove the `.mc-upgrade` file.'
255
255
  );
256
- for (const r of conflicts) lines.push(`- \`${r.path}\` → \`${r.path}.tib-upgrade\``);
256
+ for (const r of conflicts) lines.push(`- \`${r.path}\` → \`${r.path}.mc-upgrade\``);
257
257
  lines.push('');
258
258
  }
259
259
 
@@ -322,9 +322,9 @@ function buildFrontendComponentsPrBody(
322
322
  lines.push('### Drift warnings — manual review required');
323
323
  lines.push(
324
324
  'The following files were locally modified after scaffold installation. ' +
325
- 'New versions have been written to `{path}.tib-upgrade` — merge manually then remove the `.tib-upgrade` file.'
325
+ 'New versions have been written to `{path}.mc-upgrade` — merge manually then remove the `.mc-upgrade` file.'
326
326
  );
327
- for (const r of conflicts) lines.push(`- \`${r.path}\` → \`${r.path}.tib-upgrade\``);
327
+ for (const r of conflicts) lines.push(`- \`${r.path}\` → \`${r.path}.mc-upgrade\``);
328
328
  lines.push('');
329
329
  }
330
330
 
@@ -512,14 +512,20 @@ export async function runUpgrade(
512
512
  continue;
513
513
  }
514
514
 
515
- // Add to results
516
- allResults.push({ path: installPath, status: installResult.status, module: mod.id });
517
-
518
- // update-available: do NOT update manifest (file was not written to disk)
515
+ // update-available: write the proposed new content beside the drifted file
516
+ // for manual merge, but do NOT update the manifest or overwrite the file.
519
517
  if (installResult.status === 'update-available') {
518
+ if (!opts.dryRun) {
519
+ await mkdir(dirname(diskPath), { recursive: true });
520
+ await writeFile(`${diskPath}.mc-upgrade`, newContent, 'utf-8');
521
+ }
522
+ allResults.push({ path: installPath, status: 'conflict', module: mod.id });
520
523
  continue;
521
524
  }
522
525
 
526
+ // Add to results
527
+ allResults.push({ path: installPath, status: installResult.status, module: mod.id });
528
+
523
529
  // Update manifest if file was not skipped
524
530
  const newChecksum = computeChecksumString(newContent);
525
531
  upsertManifestFile(updatedManifest, {
@@ -566,7 +572,7 @@ export async function runUpgrade(
566
572
  if (conflicts.length > 0) {
567
573
  console.log('\nDrift warnings:');
568
574
  for (const r of conflicts) {
569
- console.log(` ${r.path} — new version written to ${r.path}.tib-upgrade`);
575
+ console.log(` ${r.path} — new version written to ${r.path}.mc-upgrade`);
570
576
  }
571
577
  }
572
578
 
@@ -601,7 +607,7 @@ export async function runUpgrade(
601
607
  await writeScaffoldConfig(projectDir, updatedConfig);
602
608
 
603
609
  // 10. Commit on upgrade branch
604
- const branchName = `tib-upgrade/${targetVersion}`;
610
+ const branchName = `mc-upgrade/${targetVersion}`;
605
611
  try {
606
612
  execSync(`git -C "${projectDir}" checkout -b "${branchName}"`, {
607
613
  stdio: ['pipe', 'pipe', 'pipe'],
@@ -1,11 +1,32 @@
1
1
  import { stat } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
- import type { RegistryEntry } from '@mettlecast/domain-cdk-packer';
3
+ import type {
4
+ RegistryEntry,
5
+ ActionRegistryEntry,
6
+ } from '@mettlecast/domain-cdk-packer';
4
7
  import { validRange } from 'semver';
5
8
  import type { DomainModuleConfig } from '@mettlecast/domain-runtime/types';
6
9
  import { buildRegistry } from '../builder/build-registry.js';
7
10
  import { cliLogger } from '../utils/logger.js';
8
11
 
12
+ type ActionApiExposureWithDeclaration = {
13
+ type: 'api';
14
+ path: string;
15
+ method: string;
16
+ auth: 'required' | 'none' | 'service';
17
+ tenancy: 'required' | 'none' | 'system';
18
+ roles?: string[];
19
+ securityException?: { reason: string };
20
+ authDeclared?: boolean;
21
+ tenancyDeclared?: boolean;
22
+ };
23
+
24
+ type ActionRegistryEntryWithDeclaration = ActionRegistryEntry & {
25
+ backendAccess?: 'private' | 'domain' | 'platform';
26
+ exposureDeclared?: boolean;
27
+ exposure: { type: 'internal' } | ActionApiExposureWithDeclaration;
28
+ };
29
+
9
30
  /**
10
31
  * A single validation failure.
11
32
  */
@@ -116,6 +137,199 @@ async function checkRawPathViolations(
116
137
  return [];
117
138
  }
118
139
 
140
+ /**
141
+ * Deployment-time security checks (#4662 Task D).
142
+ *
143
+ * Mirrors the invariants enforced at CDK synth time by
144
+ * `SecurityAssertionAspect` in `@mettlecast/domain-cdk-packer`. Running
145
+ * them at the CLI stage means developers get a structured error code in
146
+ * their terminal and CI fails on `mc-domain-module validate` BEFORE a
147
+ * (potentially expensive) `cdk synth` is attempted.
148
+ *
149
+ * Each rule maps 1:1 to an aspect annotation code so downstream tooling
150
+ * can correlate build-time and synth-time failures.
151
+ *
152
+ * NOTE on `SECURITY_MISSING_SECURITY_EXCEPTION` for legacy `defineApi`
153
+ * entries: the registry builder does not propagate `securityException`
154
+ * from legacy source — that field only exists on action-style API
155
+ * exposures, which are covered by the existing `AUTH_NONE_REQUIRES_EXCEPTION`
156
+ * rule in `checkActionFirstSecurity`. The aspect's corresponding check
157
+ * (`assertAnonymousRoutesHaveException`) catches the legacy case at
158
+ * synth time so a developer migrating to action-style APIs gets the
159
+ * right guidance at the right layer.
160
+ */
161
+ function checkDeploymentSecurity(
162
+ apis: import('@mettlecast/domain-cdk-packer').ApiRegistryEntry[],
163
+ actions: ActionRegistryEntry[],
164
+ ): ValidationError[] {
165
+ const errors: ValidationError[] = [];
166
+
167
+ for (const api of apis) {
168
+ // Non-anonymous APIs whose path lacks the tenant placeholder cannot
169
+ // bind `ctx.tenant.id` from the URL. The aspect surfaces this as
170
+ // `SECURITY_MISSING_TENANT_PATH`; the CLI version is a faster gate so
171
+ // CI doesn't pay the synth cost.
172
+ if (api.authType !== 'none' && !api.path.includes('/v1/tenants/{tenantId}/')) {
173
+ errors.push({
174
+ code: 'SECURITY_MISSING_TENANT_PATH',
175
+ message: `API '${api.id}' (${api.method} ${api.path}) is JWT-protected but its path does not include the canonical '/v1/tenants/{tenantId}/' placeholder. The runtime cannot bind ctx.tenant.id from a path that lacks the placeholder.`,
176
+ });
177
+ }
178
+ }
179
+
180
+ // Action API exposures are already covered by `checkActionFirstSecurity`
181
+ // for `tenancy: 'required'` paths and `auth: 'none'` exceptions. The
182
+ // codes there (TENANT_API_PATH_REQUIRED, AUTH_NONE_REQUIRES_EXCEPTION)
183
+ // are kept stable for back-compat — they map to the same aspect codes.
184
+
185
+ // Suppress the unused-actions lint while keeping the parameter shape
186
+ // for the future where actions gain new deployment-time invariants.
187
+ void actions;
188
+
189
+ return errors;
190
+ }
191
+
192
+ /**
193
+ * Action-first security validation rules (#4619, Wave 6 Task 6.1).
194
+ *
195
+ * These checks enforce the action-first security model on the
196
+ * serialised DomainRegistry produced by `buildRegistry`. The rules
197
+ * intentionally mirror the Zod cross-field refinements defined in
198
+ * `@mettlecast/domain-runtime/primitives/action` (`ApiExposureSchema`)
199
+ * so violations are caught at build time, not at runtime.
200
+ *
201
+ * Each rule emits a structured `ValidationError` whose `code` is the
202
+ * rule ID listed in the spec (e.g. `ACTION_EXPOSURE_REQUIRED`,
203
+ * `TENANT_API_PATH_REQUIRED`). Messages include the offending action
204
+ * or API id and the actionable fix.
205
+ */
206
+ function checkActionFirstSecurity(actions: ActionRegistryEntry[]): ValidationError[] {
207
+ const errors: ValidationError[] = [];
208
+
209
+ for (const action of actions as ActionRegistryEntryWithDeclaration[]) {
210
+ const id = action.id;
211
+
212
+ // ACTION_EXPOSURE_REQUIRED — every action registry entry must have
213
+ // `exposure` declared in source. The builder defaults missing
214
+ // exposure to `{ type: 'internal' }` for backwards compatibility,
215
+ // and flags the entry with `exposureDeclared: false` so the validator
216
+ // can surface it. Legacy actions that use `visibility` only (no
217
+ // `backendAccess`) are also allowed to default during migration.
218
+ if (action.exposureDeclared === false && action.backendAccess !== 'private') {
219
+ // Only fire for non-private actions: a private action with no
220
+ // exposure is the natural migration state for legacy
221
+ // visibility:'private' handlers, and forcing exposure would
222
+ // produce noisy errors during the migration window.
223
+ // (When Wave 7+ removes the legacy visibility alias this branch
224
+ // becomes a hard error for every action.)
225
+ errors.push({
226
+ code: 'ACTION_EXPOSURE_REQUIRED',
227
+ message: `Action '${id}' has no explicit \`exposure\`. New-style actions must declare \`exposure\` (e.g. \`{ type: 'api', path: '...', method: 'POST', auth: 'required', tenancy: 'required' }\`) or \`{ type: 'internal' }\` to opt out of API exposure.`,
228
+ });
229
+ }
230
+
231
+ if (action.exposure.type === 'api') {
232
+ errors.push(...checkApiExposureSecurity(action.id, action.exposure));
233
+ }
234
+ }
235
+
236
+ return errors;
237
+ }
238
+
239
+ /**
240
+ * Validate a single API-exposed action. Pulled out as a separate helper
241
+ * so each sub-rule is independently testable and the messages stay short.
242
+ */
243
+ function checkApiExposureSecurity(actionId: string, exposure: ActionApiExposureWithDeclaration): ValidationError[] {
244
+ const errors: ValidationError[] = [];
245
+
246
+ // API_EXPOSURE_AUTH_REQUIRED — API-exposed actions must declare auth
247
+ // explicitly. The builder defaults auth to 'required' when the source
248
+ // omits it; the validator surfaces the omission so developers are not
249
+ // silently relying on the safe-default.
250
+ if (exposure.authDeclared === false) {
251
+ errors.push({
252
+ code: 'API_EXPOSURE_AUTH_REQUIRED',
253
+ message: `Action '${actionId}' has \`exposure.type: 'api'\` but \`exposure.auth\` is not declared. Set \`exposure.auth\` to 'required' | 'none' | 'service' explicitly.`,
254
+ });
255
+ }
256
+
257
+ // TENANT_API_PATH_REQUIRED — API-exposed actions with `tenancy: 'required'`
258
+ // must use a path that includes the canonical tenant placeholder so the
259
+ // runtime can bind `ctx.tenant.id` from the URL.
260
+ if (exposure.tenancy === 'required' && !exposure.path.includes('/v1/tenants/{tenantId}/')) {
261
+ errors.push({
262
+ code: 'TENANT_API_PATH_REQUIRED',
263
+ message: `Action '${actionId}' declares \`exposure.tenancy: 'required'\` but \`exposure.path\` ('${exposure.path}') does not include the canonical tenant placeholder '/v1/tenants/{tenantId}/'.`,
264
+ });
265
+ }
266
+
267
+ // AUTH_NONE_REQUIRES_EXCEPTION — `auth: 'none'` (anonymous) routes
268
+ // must carry an explicit `securityException` with a non-empty reason
269
+ // so security reviewers can audit the relaxation.
270
+ if (exposure.auth === 'none') {
271
+ const reason = exposure.securityException?.reason;
272
+ if (!reason || reason.trim().length === 0) {
273
+ errors.push({
274
+ code: 'AUTH_NONE_REQUIRES_EXCEPTION',
275
+ message: `Action '${actionId}' has \`exposure.auth: 'none'\` but no \`exposure.securityException.reason\`. Public/anonymous routes must document the security exception with a non-empty reason (and ideally a tracking reference).`,
276
+ });
277
+ }
278
+ }
279
+
280
+ // TENANCY_NONE_REQUIRES_REASON_WHEN_PUBLIC — `tenancy: 'none'` on a
281
+ // public API route must justify the missing tenant context. If the
282
+ // route is already justified as anonymous via `auth: 'none'` the
283
+ // same `securityException` may be reused; otherwise an exception is
284
+ // required for tenancy: 'none' on its own.
285
+ if (exposure.tenancy === 'none') {
286
+ const reason = exposure.securityException?.reason;
287
+ if (!reason || reason.trim().length === 0) {
288
+ errors.push({
289
+ code: 'TENANCY_NONE_REQUIRES_REASON_WHEN_PUBLIC',
290
+ message: `Action '${actionId}' has \`exposure.tenancy: 'none'\` but no \`exposure.securityException.reason\`. Routes without tenant context must document the security exception with a non-empty reason.`,
291
+ });
292
+ }
293
+ }
294
+
295
+ // SYSTEM_API_REQUIRES_ROLE — `tenancy: 'system'` combined with
296
+ // `auth: 'required'` must declare non-empty `roles` so the JWT
297
+ // authorizer can scope the call. Routes that use `auth: 'service'`
298
+ // are service-only and do not require role narrowing.
299
+ if (exposure.tenancy === 'system' && exposure.auth === 'required') {
300
+ if (!Array.isArray(exposure.roles) || exposure.roles.length === 0) {
301
+ errors.push({
302
+ code: 'SYSTEM_API_REQUIRES_ROLE',
303
+ message: `Action '${actionId}' has \`exposure.tenancy: 'system'\` and \`exposure.auth: 'required'\` but no \`exposure.roles\`. System-tenancy routes using user auth must declare at least one required role.`,
304
+ });
305
+ }
306
+ }
307
+
308
+ return errors;
309
+ }
310
+
311
+ /**
312
+ * DEFINE_API_LEGACY_USAGE — the action-first migration replaces
313
+ * standalone `defineApi` calls with `defineAction` + `exposure.type:
314
+ * 'api'`. Every `defineApi` call still in the source produces a row in
315
+ * `registry.apis`; the validator emits one error per legacy API to
316
+ * enforce the alpha breaking migration. The validate command's fixture
317
+ * suite intentionally does not use `defineApi`, so this rule does not
318
+ * break existing unit tests.
319
+ */
320
+ function checkDefineApiLegacyUsage(apiCount: number, apis: RegistryEntry[]): ValidationError[] {
321
+ if (apiCount === 0) return [];
322
+ const errors: ValidationError[] = [];
323
+ for (const api of apis) {
324
+ if (api.kind !== 'api') continue;
325
+ errors.push({
326
+ code: 'DEFINE_API_LEGACY_USAGE',
327
+ message: `API '${api.id}' uses legacy \`defineApi\`. Migrate to \`defineAction\` with \`exposure: { type: 'api', path, method, auth, tenancy }\` (#4619).`,
328
+ });
329
+ }
330
+ return errors;
331
+ }
332
+
119
333
  /**
120
334
  * Run the validate command: build the registry and perform structural validation.
121
335
  * Exits the process with code 1 if validation fails (CI gate usage).
@@ -213,6 +427,17 @@ export async function runValidate(
213
427
  const rawPathErrors = await checkRawPathViolations(registry.domainRoot, config);
214
428
  errors.push(...rawPathErrors);
215
429
 
430
+ // Wave 6 Task 6.1: action-first security validation gates (#4619).
431
+ // Runs against the registry built above so the rules see the same
432
+ // shape the CDK packer will eventually consume.
433
+ errors.push(...checkActionFirstSecurity(registry.actions));
434
+ errors.push(...checkDefineApiLegacyUsage(registry.apis.length, registry.apis));
435
+
436
+ // Issue #4662 Task D — deployment-time security gates. These mirror
437
+ // the CDK synth-time `SecurityAssertionAspect` so violations are
438
+ // caught before any AWS deployment is attempted.
439
+ errors.push(...checkDeploymentSecurity(registry.apis, registry.actions));
440
+
216
441
  const totalPrimitives = registry.apis.length + registry.webhooks.length +
217
442
  registry.subscribers.length + registry.schedules.length +
218
443
  registry.jobs.length + registry.actions.length;
@@ -68,7 +68,7 @@ function injectLineCommentHeader(
68
68
  ): string {
69
69
  const header = [
70
70
  `${commentChar} @mc-scaffold: ${moduleId}@${version}`,
71
- `${commentChar} This file is managed by TIB scaffold. Manual edits will be flagged during \`tib upgrade\`.`,
71
+ `${commentChar} This file is managed by TIB scaffold. Manual edits will be flagged during \`npx mc-domain-module upgrade\`.`,
72
72
  `${commentChar} To opt out of upgrade management for this file, remove these header lines.`,
73
73
  '',
74
74
  ].join('\n');
@@ -92,7 +92,7 @@ function injectBlockCommentHeader(
92
92
  ): string {
93
93
  const header = [
94
94
  `${open} @mc-scaffold: ${moduleId}@${version} ${close}`,
95
- `${open} This file is managed by TIB scaffold. Manual edits will be flagged during \`tib upgrade\`. ${close}`,
95
+ `${open} This file is managed by TIB scaffold. Manual edits will be flagged during \`npx mc-domain-module upgrade\`. ${close}`,
96
96
  '',
97
97
  ].join('\n');
98
98
  return header + content;
@@ -14,7 +14,7 @@ export interface InstallResult {
14
14
  *
15
15
  * - managed: always overwrite. Return 'added' if new, 'updated' if existed.
16
16
  * - editable: check sha256. If same → 'unchanged'. If different → 'update-available'
17
- * (does not write .tib-upgrade or overwrite — user opts in via Updates tab).
17
+ * (does not write .mc-upgrade or overwrite — user opts in via Updates tab).
18
18
  * - seed: if file exists → 'skipped'. Else write → 'added'.
19
19
  */
20
20
  export async function installScaffoldFile(
@@ -29,10 +29,9 @@ const MANIFEST_PATH = '.mc/manifest.json';
29
29
  const SCHEMA_URL = 'https://mc-scaffold.s3.amazonaws.com/schema/manifest.v3.json';
30
30
 
31
31
  export function inferPolicyFromPath(filePath: string): FilePolicy {
32
- // owned: infra/, .mc/infra/, mc-deploy.yml, .github/workflows/
32
+ // owned: infra/, mc-deploy.yml, .github/workflows/
33
33
  if (
34
34
  filePath.match(/^infra\//) ||
35
- filePath.match(/^\.tib\/infra\//) ||
36
35
  filePath === 'mc-deploy.yml' ||
37
36
  filePath.match(/^\.github\/workflows\//)
38
37
  ) {
@@ -92,9 +92,9 @@ export interface ScaffoldConfig {
92
92
  awsAccountId: string;
93
93
  /** Admin email collected at scaffold time (Cognito seed user). Present only when auth module is enabled. */
94
94
  adminEmail?: string;
95
- /** Domain IDs added via `tib add-domain` — NOT scaffold-owned, user-managed */
95
+ /** Domain IDs added via `mc-domain-module add-domain` — NOT scaffold-owned, user-managed */
96
96
  domainIds: string[];
97
- /** Flow IDs added via `tib add-flow` — user-managed */
97
+ /** Flow IDs added via `mc-domain-module add-flow` — user-managed */
98
98
  flowIds: string[];
99
99
  /** S3 bucket used for scaffold fetches (defaults to public TIB bucket) */
100
100
  scaffoldBucket: string;
@@ -148,7 +148,7 @@ export async function readScaffoldConfig(projectRoot: string): Promise<ScaffoldC
148
148
  if (!raw.scaffoldVersion) {
149
149
  // eslint-disable-next-line no-console
150
150
  console.warn(
151
- '[tib] scaffold-config.json is missing scaffoldVersion — project was created before S3 scaffold migration. Run `tib upgrade` to backfill.'
151
+ '[mc-domain-module] scaffold-config.json is missing scaffoldVersion — project was created before S3 scaffold migration. Run `npx mc-domain-module upgrade` to backfill.'
152
152
  );
153
153
  }
154
154