@mettlecast/domain-cli 0.2.58 → 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
@@ -5,7 +5,7 @@ function toZoneLine(r) {
5
5
  return `${r.name}. 300 IN ${r.type} ${r.value}.`;
6
6
  }
7
7
  /**
8
- * tib show-dns [--env dev|staging|prod] [--project-dir <path>]
8
+ * mc-domain-module show-dns [--env dev|staging|prod] [--project-dir <path>]
9
9
  *
10
10
  * Reads the scaffold-config.json and prints DNS records that must be added for
11
11
  * custom domain wiring: certificate validation CNAMEs (step 1) and traffic
@@ -16,25 +16,25 @@ function toZoneLine(r) {
16
16
  */
17
17
  export async function runShowDns(opts) {
18
18
  const projectDir = opts.projectDir ? path.resolve(opts.projectDir) : process.cwd();
19
- const configPath = path.join(projectDir, '.tib', 'scaffold-config.json');
19
+ const configPath = path.join(projectDir, '.mc', 'scaffold-config.json');
20
20
  let config;
21
21
  try {
22
22
  const raw = await readFile(configPath, 'utf-8');
23
23
  config = JSON.parse(raw);
24
24
  }
25
25
  catch {
26
- console.error('[tib] Could not read .mc/scaffold-config.json — run from a TIB project root.');
26
+ console.error('[mc-domain-module] Could not read .mc/scaffold-config.json — run from a Mettlecast project root.');
27
27
  process.exit(1);
28
28
  }
29
29
  const environments = config.environments ?? {};
30
30
  if (Object.keys(environments).length === 0) {
31
- console.log('[tib] No environments configured. Set a custom domain via `tib show-dns` or the Setup tab.');
31
+ console.log('[mc-domain-module] No environments configured. Set a custom domain via `npx mc-domain-module show-dns` or the Setup tab.');
32
32
  return;
33
33
  }
34
34
  // Try to load CDK outputs for routing records
35
35
  let cdkOutputs = {};
36
36
  try {
37
- const outputsPath = path.join(projectDir, '.tib', 'cdk-outputs.json');
37
+ const outputsPath = path.join(projectDir, '.mc', 'cdk-outputs.json');
38
38
  const raw = await readFile(outputsPath, 'utf-8');
39
39
  cdkOutputs = JSON.parse(raw);
40
40
  }
@@ -1,7 +1,7 @@
1
1
  import { createGunzip } from 'node:zlib';
2
2
  import { Readable } from 'node:stream';
3
- import { unlink } from 'node:fs/promises';
4
- import { join, resolve } from 'node:path';
3
+ import { mkdir, writeFile, unlink } from 'node:fs/promises';
4
+ import { join, resolve, dirname } from 'node:path';
5
5
  import { execSync } from 'node:child_process';
6
6
  import ky from 'ky';
7
7
  import { cliLogger } from '../utils/logger.js';
@@ -176,9 +176,9 @@ function buildPrBody(currentVersion, targetVersion, results, changelogUrl) {
176
176
  if (conflicts.length > 0) {
177
177
  lines.push('### Drift warnings — manual review required');
178
178
  lines.push('The following files were locally modified after scaffold installation. ' +
179
- 'New versions have been written to `{path}.tib-upgrade` — merge manually then remove the `.tib-upgrade` file.');
179
+ 'New versions have been written to `{path}.mc-upgrade` — merge manually then remove the `.mc-upgrade` file.');
180
180
  for (const r of conflicts)
181
- lines.push(`- \`${r.path}\` → \`${r.path}.tib-upgrade\``);
181
+ lines.push(`- \`${r.path}\` → \`${r.path}.mc-upgrade\``);
182
182
  lines.push('');
183
183
  }
184
184
  if (changelogUrl) {
@@ -235,9 +235,9 @@ function buildFrontendComponentsPrBody(currentVersion, targetVersion, results, c
235
235
  if (conflicts.length > 0) {
236
236
  lines.push('### Drift warnings — manual review required');
237
237
  lines.push('The following files were locally modified after scaffold installation. ' +
238
- 'New versions have been written to `{path}.tib-upgrade` — merge manually then remove the `.tib-upgrade` file.');
238
+ 'New versions have been written to `{path}.mc-upgrade` — merge manually then remove the `.mc-upgrade` file.');
239
239
  for (const r of conflicts)
240
- lines.push(`- \`${r.path}\` → \`${r.path}.tib-upgrade\``);
240
+ lines.push(`- \`${r.path}\` → \`${r.path}.mc-upgrade\``);
241
241
  lines.push('');
242
242
  }
243
243
  if (changelogUrl) {
@@ -367,12 +367,18 @@ export async function runUpgrade(packageSpec, opts) {
367
367
  // Skipped files are not tracked in results (e.g., seed file with --frontend-components)
368
368
  continue;
369
369
  }
370
- // Add to results
371
- allResults.push({ path: installPath, status: installResult.status, module: mod.id });
372
- // update-available: do NOT update manifest (file was not written to disk)
370
+ // update-available: write the proposed new content beside the drifted file
371
+ // for manual merge, but do NOT update the manifest or overwrite the file.
373
372
  if (installResult.status === 'update-available') {
373
+ if (!opts.dryRun) {
374
+ await mkdir(dirname(diskPath), { recursive: true });
375
+ await writeFile(`${diskPath}.mc-upgrade`, newContent, 'utf-8');
376
+ }
377
+ allResults.push({ path: installPath, status: 'conflict', module: mod.id });
374
378
  continue;
375
379
  }
380
+ // Add to results
381
+ allResults.push({ path: installPath, status: installResult.status, module: mod.id });
376
382
  // Update manifest if file was not skipped
377
383
  const newChecksum = computeChecksumString(newContent);
378
384
  upsertManifestFile(updatedManifest, {
@@ -417,7 +423,7 @@ export async function runUpgrade(packageSpec, opts) {
417
423
  if (conflicts.length > 0) {
418
424
  console.log('\nDrift warnings:');
419
425
  for (const r of conflicts) {
420
- console.log(` ${r.path} — new version written to ${r.path}.tib-upgrade`);
426
+ console.log(` ${r.path} — new version written to ${r.path}.mc-upgrade`);
421
427
  }
422
428
  }
423
429
  if (opts.check) {
@@ -452,7 +458,7 @@ export async function runUpgrade(packageSpec, opts) {
452
458
  const updatedConfig = { ...scaffoldConfig, scaffoldVersion: targetVersion };
453
459
  await writeScaffoldConfig(projectDir, updatedConfig);
454
460
  // 10. Commit on upgrade branch
455
- const branchName = `tib-upgrade/${targetVersion}`;
461
+ const branchName = `mc-upgrade/${targetVersion}`;
456
462
  try {
457
463
  execSync(`git -C "${projectDir}" checkout -b "${branchName}"`, {
458
464
  stdio: ['pipe', 'pipe', 'pipe'],
@@ -78,6 +78,180 @@ async function checkRawPathViolations(_repoRoot, config) {
78
78
  return [];
79
79
  return [];
80
80
  }
81
+ /**
82
+ * Deployment-time security checks (#4662 Task D).
83
+ *
84
+ * Mirrors the invariants enforced at CDK synth time by
85
+ * `SecurityAssertionAspect` in `@mettlecast/domain-cdk-packer`. Running
86
+ * them at the CLI stage means developers get a structured error code in
87
+ * their terminal and CI fails on `mc-domain-module validate` BEFORE a
88
+ * (potentially expensive) `cdk synth` is attempted.
89
+ *
90
+ * Each rule maps 1:1 to an aspect annotation code so downstream tooling
91
+ * can correlate build-time and synth-time failures.
92
+ *
93
+ * NOTE on `SECURITY_MISSING_SECURITY_EXCEPTION` for legacy `defineApi`
94
+ * entries: the registry builder does not propagate `securityException`
95
+ * from legacy source — that field only exists on action-style API
96
+ * exposures, which are covered by the existing `AUTH_NONE_REQUIRES_EXCEPTION`
97
+ * rule in `checkActionFirstSecurity`. The aspect's corresponding check
98
+ * (`assertAnonymousRoutesHaveException`) catches the legacy case at
99
+ * synth time so a developer migrating to action-style APIs gets the
100
+ * right guidance at the right layer.
101
+ */
102
+ function checkDeploymentSecurity(apis, actions) {
103
+ const errors = [];
104
+ for (const api of apis) {
105
+ // Non-anonymous APIs whose path lacks the tenant placeholder cannot
106
+ // bind `ctx.tenant.id` from the URL. The aspect surfaces this as
107
+ // `SECURITY_MISSING_TENANT_PATH`; the CLI version is a faster gate so
108
+ // CI doesn't pay the synth cost.
109
+ if (api.authType !== 'none' && !api.path.includes('/v1/tenants/{tenantId}/')) {
110
+ errors.push({
111
+ code: 'SECURITY_MISSING_TENANT_PATH',
112
+ 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.`,
113
+ });
114
+ }
115
+ }
116
+ // Action API exposures are already covered by `checkActionFirstSecurity`
117
+ // for `tenancy: 'required'` paths and `auth: 'none'` exceptions. The
118
+ // codes there (TENANT_API_PATH_REQUIRED, AUTH_NONE_REQUIRES_EXCEPTION)
119
+ // are kept stable for back-compat — they map to the same aspect codes.
120
+ // Suppress the unused-actions lint while keeping the parameter shape
121
+ // for the future where actions gain new deployment-time invariants.
122
+ void actions;
123
+ return errors;
124
+ }
125
+ /**
126
+ * Action-first security validation rules (#4619, Wave 6 Task 6.1).
127
+ *
128
+ * These checks enforce the action-first security model on the
129
+ * serialised DomainRegistry produced by `buildRegistry`. The rules
130
+ * intentionally mirror the Zod cross-field refinements defined in
131
+ * `@mettlecast/domain-runtime/primitives/action` (`ApiExposureSchema`)
132
+ * so violations are caught at build time, not at runtime.
133
+ *
134
+ * Each rule emits a structured `ValidationError` whose `code` is the
135
+ * rule ID listed in the spec (e.g. `ACTION_EXPOSURE_REQUIRED`,
136
+ * `TENANT_API_PATH_REQUIRED`). Messages include the offending action
137
+ * or API id and the actionable fix.
138
+ */
139
+ function checkActionFirstSecurity(actions) {
140
+ const errors = [];
141
+ for (const action of actions) {
142
+ const id = action.id;
143
+ // ACTION_EXPOSURE_REQUIRED — every action registry entry must have
144
+ // `exposure` declared in source. The builder defaults missing
145
+ // exposure to `{ type: 'internal' }` for backwards compatibility,
146
+ // and flags the entry with `exposureDeclared: false` so the validator
147
+ // can surface it. Legacy actions that use `visibility` only (no
148
+ // `backendAccess`) are also allowed to default during migration.
149
+ if (action.exposureDeclared === false && action.backendAccess !== 'private') {
150
+ // Only fire for non-private actions: a private action with no
151
+ // exposure is the natural migration state for legacy
152
+ // visibility:'private' handlers, and forcing exposure would
153
+ // produce noisy errors during the migration window.
154
+ // (When Wave 7+ removes the legacy visibility alias this branch
155
+ // becomes a hard error for every action.)
156
+ errors.push({
157
+ code: 'ACTION_EXPOSURE_REQUIRED',
158
+ 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.`,
159
+ });
160
+ }
161
+ if (action.exposure.type === 'api') {
162
+ errors.push(...checkApiExposureSecurity(action.id, action.exposure));
163
+ }
164
+ }
165
+ return errors;
166
+ }
167
+ /**
168
+ * Validate a single API-exposed action. Pulled out as a separate helper
169
+ * so each sub-rule is independently testable and the messages stay short.
170
+ */
171
+ function checkApiExposureSecurity(actionId, exposure) {
172
+ const errors = [];
173
+ // API_EXPOSURE_AUTH_REQUIRED — API-exposed actions must declare auth
174
+ // explicitly. The builder defaults auth to 'required' when the source
175
+ // omits it; the validator surfaces the omission so developers are not
176
+ // silently relying on the safe-default.
177
+ if (exposure.authDeclared === false) {
178
+ errors.push({
179
+ code: 'API_EXPOSURE_AUTH_REQUIRED',
180
+ message: `Action '${actionId}' has \`exposure.type: 'api'\` but \`exposure.auth\` is not declared. Set \`exposure.auth\` to 'required' | 'none' | 'service' explicitly.`,
181
+ });
182
+ }
183
+ // TENANT_API_PATH_REQUIRED — API-exposed actions with `tenancy: 'required'`
184
+ // must use a path that includes the canonical tenant placeholder so the
185
+ // runtime can bind `ctx.tenant.id` from the URL.
186
+ if (exposure.tenancy === 'required' && !exposure.path.includes('/v1/tenants/{tenantId}/')) {
187
+ errors.push({
188
+ code: 'TENANT_API_PATH_REQUIRED',
189
+ message: `Action '${actionId}' declares \`exposure.tenancy: 'required'\` but \`exposure.path\` ('${exposure.path}') does not include the canonical tenant placeholder '/v1/tenants/{tenantId}/'.`,
190
+ });
191
+ }
192
+ // AUTH_NONE_REQUIRES_EXCEPTION — `auth: 'none'` (anonymous) routes
193
+ // must carry an explicit `securityException` with a non-empty reason
194
+ // so security reviewers can audit the relaxation.
195
+ if (exposure.auth === 'none') {
196
+ const reason = exposure.securityException?.reason;
197
+ if (!reason || reason.trim().length === 0) {
198
+ errors.push({
199
+ code: 'AUTH_NONE_REQUIRES_EXCEPTION',
200
+ 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).`,
201
+ });
202
+ }
203
+ }
204
+ // TENANCY_NONE_REQUIRES_REASON_WHEN_PUBLIC — `tenancy: 'none'` on a
205
+ // public API route must justify the missing tenant context. If the
206
+ // route is already justified as anonymous via `auth: 'none'` the
207
+ // same `securityException` may be reused; otherwise an exception is
208
+ // required for tenancy: 'none' on its own.
209
+ if (exposure.tenancy === 'none') {
210
+ const reason = exposure.securityException?.reason;
211
+ if (!reason || reason.trim().length === 0) {
212
+ errors.push({
213
+ code: 'TENANCY_NONE_REQUIRES_REASON_WHEN_PUBLIC',
214
+ 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.`,
215
+ });
216
+ }
217
+ }
218
+ // SYSTEM_API_REQUIRES_ROLE — `tenancy: 'system'` combined with
219
+ // `auth: 'required'` must declare non-empty `roles` so the JWT
220
+ // authorizer can scope the call. Routes that use `auth: 'service'`
221
+ // are service-only and do not require role narrowing.
222
+ if (exposure.tenancy === 'system' && exposure.auth === 'required') {
223
+ if (!Array.isArray(exposure.roles) || exposure.roles.length === 0) {
224
+ errors.push({
225
+ code: 'SYSTEM_API_REQUIRES_ROLE',
226
+ 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.`,
227
+ });
228
+ }
229
+ }
230
+ return errors;
231
+ }
232
+ /**
233
+ * DEFINE_API_LEGACY_USAGE — the action-first migration replaces
234
+ * standalone `defineApi` calls with `defineAction` + `exposure.type:
235
+ * 'api'`. Every `defineApi` call still in the source produces a row in
236
+ * `registry.apis`; the validator emits one error per legacy API to
237
+ * enforce the alpha breaking migration. The validate command's fixture
238
+ * suite intentionally does not use `defineApi`, so this rule does not
239
+ * break existing unit tests.
240
+ */
241
+ function checkDefineApiLegacyUsage(apiCount, apis) {
242
+ if (apiCount === 0)
243
+ return [];
244
+ const errors = [];
245
+ for (const api of apis) {
246
+ if (api.kind !== 'api')
247
+ continue;
248
+ errors.push({
249
+ code: 'DEFINE_API_LEGACY_USAGE',
250
+ message: `API '${api.id}' uses legacy \`defineApi\`. Migrate to \`defineAction\` with \`exposure: { type: 'api', path, method, auth, tenancy }\` (#4619).`,
251
+ });
252
+ }
253
+ return errors;
254
+ }
81
255
  /**
82
256
  * Run the validate command: build the registry and perform structural validation.
83
257
  * Exits the process with code 1 if validation fails (CI gate usage).
@@ -155,6 +329,15 @@ export async function runValidate(domainRoot, exitOnFailure = true, config = { m
155
329
  }
156
330
  const rawPathErrors = await checkRawPathViolations(registry.domainRoot, config);
157
331
  errors.push(...rawPathErrors);
332
+ // Wave 6 Task 6.1: action-first security validation gates (#4619).
333
+ // Runs against the registry built above so the rules see the same
334
+ // shape the CDK packer will eventually consume.
335
+ errors.push(...checkActionFirstSecurity(registry.actions));
336
+ errors.push(...checkDefineApiLegacyUsage(registry.apis.length, registry.apis));
337
+ // Issue #4662 Task D — deployment-time security gates. These mirror
338
+ // the CDK synth-time `SecurityAssertionAspect` so violations are
339
+ // caught before any AWS deployment is attempted.
340
+ errors.push(...checkDeploymentSecurity(registry.apis, registry.actions));
158
341
  const totalPrimitives = registry.apis.length + registry.webhooks.length +
159
342
  registry.subscribers.length + registry.schedules.length +
160
343
  registry.jobs.length + registry.actions.length;
@@ -47,7 +47,7 @@ export function injectHeader(content, filename, moduleId, version) {
47
47
  function injectLineCommentHeader(content, commentChar, moduleId, version) {
48
48
  const header = [
49
49
  `${commentChar} @mc-scaffold: ${moduleId}@${version}`,
50
- `${commentChar} This file is managed by TIB scaffold. Manual edits will be flagged during \`tib upgrade\`.`,
50
+ `${commentChar} This file is managed by TIB scaffold. Manual edits will be flagged during \`npx mc-domain-module upgrade\`.`,
51
51
  `${commentChar} To opt out of upgrade management for this file, remove these header lines.`,
52
52
  '',
53
53
  ].join('\n');
@@ -63,7 +63,7 @@ function injectLineCommentHeader(content, commentChar, moduleId, version) {
63
63
  function injectBlockCommentHeader(content, open, close, moduleId, version) {
64
64
  const header = [
65
65
  `${open} @mc-scaffold: ${moduleId}@${version} ${close}`,
66
- `${open} This file is managed by TIB scaffold. Manual edits will be flagged during \`tib upgrade\`. ${close}`,
66
+ `${open} This file is managed by TIB scaffold. Manual edits will be flagged during \`npx mc-domain-module upgrade\`. ${close}`,
67
67
  '',
68
68
  ].join('\n');
69
69
  return header + content;
@@ -8,7 +8,7 @@ export interface InstallResult {
8
8
  *
9
9
  * - managed: always overwrite. Return 'added' if new, 'updated' if existed.
10
10
  * - editable: check sha256. If same → 'unchanged'. If different → 'update-available'
11
- * (does not write .tib-upgrade or overwrite — user opts in via Updates tab).
11
+ * (does not write .mc-upgrade or overwrite — user opts in via Updates tab).
12
12
  * - seed: if file exists → 'skipped'. Else write → 'added'.
13
13
  */
14
14
  export declare function installScaffoldFile(absPath: string, content: string, policy: FilePolicy, currentEntry: ManifestFileEntry | undefined, opts?: {
@@ -6,7 +6,7 @@ import { computeChecksumString } from './checksum.js';
6
6
  *
7
7
  * - managed: always overwrite. Return 'added' if new, 'updated' if existed.
8
8
  * - editable: check sha256. If same → 'unchanged'. If different → 'update-available'
9
- * (does not write .tib-upgrade or overwrite — user opts in via Updates tab).
9
+ * (does not write .mc-upgrade or overwrite — user opts in via Updates tab).
10
10
  * - seed: if file exists → 'skipped'. Else write → 'added'.
11
11
  */
12
12
  export async function installScaffoldFile(absPath, content, policy, currentEntry, opts) {
@@ -4,9 +4,8 @@ import { cliLogger } from './logger.js';
4
4
  const MANIFEST_PATH = '.mc/manifest.json';
5
5
  const SCHEMA_URL = 'https://mc-scaffold.s3.amazonaws.com/schema/manifest.v3.json';
6
6
  export function inferPolicyFromPath(filePath) {
7
- // owned: infra/, .mc/infra/, mc-deploy.yml, .github/workflows/
7
+ // owned: infra/, mc-deploy.yml, .github/workflows/
8
8
  if (filePath.match(/^infra\//) ||
9
- filePath.match(/^\.tib\/infra\//) ||
10
9
  filePath === 'mc-deploy.yml' ||
11
10
  filePath.match(/^\.github\/workflows\//)) {
12
11
  return 'managed';
@@ -67,9 +67,9 @@ export interface ScaffoldConfig {
67
67
  awsAccountId: string;
68
68
  /** Admin email collected at scaffold time (Cognito seed user). Present only when auth module is enabled. */
69
69
  adminEmail?: string;
70
- /** Domain IDs added via `tib add-domain` — NOT scaffold-owned, user-managed */
70
+ /** Domain IDs added via `mc-domain-module add-domain` — NOT scaffold-owned, user-managed */
71
71
  domainIds: string[];
72
- /** Flow IDs added via `tib add-flow` — user-managed */
72
+ /** Flow IDs added via `mc-domain-module add-flow` — user-managed */
73
73
  flowIds: string[];
74
74
  /** S3 bucket used for scaffold fetches (defaults to public TIB bucket) */
75
75
  scaffoldBucket: string;
@@ -28,7 +28,7 @@ export async function readScaffoldConfig(projectRoot) {
28
28
  // Backward compatibility: old files may only have domainIds/flowIds
29
29
  if (!raw.scaffoldVersion) {
30
30
  // eslint-disable-next-line no-console
31
- console.warn('[tib] scaffold-config.json is missing scaffoldVersion — project was created before S3 scaffold migration. Run `tib upgrade` to backfill.');
31
+ console.warn('[mc-domain-module] scaffold-config.json is missing scaffoldVersion — project was created before S3 scaffold migration. Run `npx mc-domain-module upgrade` to backfill.');
32
32
  }
33
33
  return {
34
34
  ...DEFAULT_SCAFFOLD_CONFIG,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mettlecast/domain-cli",
3
- "version": "0.2.58",
3
+ "version": "0.2.60",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org",
@@ -19,7 +19,7 @@ describe('check-hashes', () => {
19
19
 
20
20
  it('should return ok: true when all files match manifest', async () => {
21
21
  // Set up directory structure
22
- await mkdir(join(tempDir, '.tib'), { recursive: true });
22
+ await mkdir(join(tempDir, '.mc'), { recursive: true });
23
23
  await mkdir(join(tempDir, 'infra', 'modules'), { recursive: true });
24
24
 
25
25
  // Create a test file
@@ -36,7 +36,7 @@ describe('check-hashes', () => {
36
36
  'infra/modules/test.ts': hash,
37
37
  },
38
38
  };
39
- await writeFile(join(tempDir, '.tib', 'modules-hashes.json'), JSON.stringify(manifest));
39
+ await writeFile(join(tempDir, '.mc', 'modules-hashes.json'), JSON.stringify(manifest));
40
40
 
41
41
  const result = await runCheckHashes({ projectRoot: tempDir });
42
42
  expect(result.ok).toBe(true);
@@ -46,7 +46,7 @@ describe('check-hashes', () => {
46
46
  });
47
47
 
48
48
  it('should detect modified files', async () => {
49
- await mkdir(join(tempDir, '.tib'), { recursive: true });
49
+ await mkdir(join(tempDir, '.mc'), { recursive: true });
50
50
  await mkdir(join(tempDir, 'infra', 'modules'), { recursive: true });
51
51
 
52
52
  const originalContent = 'export const MyStack = {};';
@@ -62,7 +62,7 @@ describe('check-hashes', () => {
62
62
  'infra/modules/test.ts': originalHash,
63
63
  },
64
64
  };
65
- await writeFile(join(tempDir, '.tib', 'modules-hashes.json'), JSON.stringify(manifest));
65
+ await writeFile(join(tempDir, '.mc', 'modules-hashes.json'), JSON.stringify(manifest));
66
66
 
67
67
  // Modify the file
68
68
  const modifiedContent = 'export const MyStack = { foo: "bar" };';
@@ -77,7 +77,7 @@ describe('check-hashes', () => {
77
77
  });
78
78
 
79
79
  it('should detect deleted files', async () => {
80
- await mkdir(join(tempDir, '.tib'), { recursive: true });
80
+ await mkdir(join(tempDir, '.mc'), { recursive: true });
81
81
  await mkdir(join(tempDir, 'infra', 'modules'), { recursive: true });
82
82
 
83
83
  const content = 'export const MyStack = {};';
@@ -94,7 +94,7 @@ describe('check-hashes', () => {
94
94
  'infra/modules/deleted.ts': 'somehash',
95
95
  },
96
96
  };
97
- await writeFile(join(tempDir, '.tib', 'modules-hashes.json'), JSON.stringify(manifest));
97
+ await writeFile(join(tempDir, '.mc', 'modules-hashes.json'), JSON.stringify(manifest));
98
98
 
99
99
  const result = await runCheckHashes({ projectRoot: tempDir });
100
100
  expect(result.ok).toBe(false);
@@ -103,7 +103,7 @@ describe('check-hashes', () => {
103
103
  });
104
104
 
105
105
  it('should detect unexpected files', async () => {
106
- await mkdir(join(tempDir, '.tib'), { recursive: true });
106
+ await mkdir(join(tempDir, '.mc'), { recursive: true });
107
107
  await mkdir(join(tempDir, 'infra', 'modules'), { recursive: true });
108
108
 
109
109
  const content = 'export const MyStack = {};';
@@ -123,7 +123,7 @@ describe('check-hashes', () => {
123
123
  'infra/modules/test.ts': hash,
124
124
  },
125
125
  };
126
- await writeFile(join(tempDir, '.tib', 'modules-hashes.json'), JSON.stringify(manifest));
126
+ await writeFile(join(tempDir, '.mc', 'modules-hashes.json'), JSON.stringify(manifest));
127
127
 
128
128
  const result = await runCheckHashes({ projectRoot: tempDir });
129
129
  expect(result.ok).toBe(false);
@@ -132,7 +132,7 @@ describe('check-hashes', () => {
132
132
  });
133
133
 
134
134
  it('should throw with helpful message when manifest is missing', async () => {
135
- await mkdir(join(tempDir, '.tib'), { recursive: true });
135
+ await mkdir(join(tempDir, '.mc'), { recursive: true });
136
136
  await mkdir(join(tempDir, 'infra', 'modules'), { recursive: true });
137
137
 
138
138
  await expect(runCheckHashes({ projectRoot: tempDir })).rejects.toThrow(
@@ -55,7 +55,7 @@ describe('upgrade command', () => {
55
55
  let tmpDir: string;
56
56
 
57
57
  beforeEach(async () => {
58
- tmpDir = await mkdtemp(join(tmpdir(), 'tib-upgrade-test-'));
58
+ tmpDir = await mkdtemp(join(tmpdir(), 'mc-upgrade-test-'));
59
59
  vi.clearAllMocks();
60
60
  });
61
61
 
@@ -213,8 +213,8 @@ describe('upgrade command', () => {
213
213
  });
214
214
  });
215
215
 
216
- describe('drift detection and .tib-upgrade conflict files', () => {
217
- it('writes .tib-upgrade file when file is drifted', async () => {
216
+ describe('drift detection and .mc-upgrade conflict files', () => {
217
+ it('writes .mc-upgrade file when file is drifted', async () => {
218
218
  const manifestChecksum = computeChecksumString('original content');
219
219
  const driftedContent = 'locally modified content by user';
220
220
  const driftedChecksum = computeChecksumString(driftedContent);
@@ -313,8 +313,8 @@ describe('upgrade command', () => {
313
313
  const originalContent = await readFile(join(tmpDir, filePath), 'utf-8');
314
314
  expect(originalContent).toBe(driftedContent);
315
315
 
316
- // Assert .tib-upgrade file was created with new content
317
- const conflictFile = await readFile(join(tmpDir, `${filePath}.tib-upgrade`), 'utf-8');
316
+ // Assert .mc-upgrade file was created with new content
317
+ const conflictFile = await readFile(join(tmpDir, `${filePath}.mc-upgrade`), 'utf-8');
318
318
  expect(conflictFile).toContain(newContent);
319
319
  });
320
320
  });
@@ -620,8 +620,8 @@ describe('upgrade command', () => {
620
620
  const originalContent = await readFile(join(tmpDir, filePath), 'utf-8');
621
621
  expect(originalContent).toBe(driftedContent);
622
622
 
623
- // .tib-upgrade file should be created
624
- const conflictFile = await readFile(join(tmpDir, `${filePath}.tib-upgrade`), 'utf-8');
623
+ // .mc-upgrade file should be created
624
+ const conflictFile = await readFile(join(tmpDir, `${filePath}.mc-upgrade`), 'utf-8');
625
625
  expect(conflictFile).toContain(newContent);
626
626
  });
627
627