@principles/pd-cli 1.129.0 → 1.130.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/commands/runtime-canary.d.ts.map +1 -1
- package/dist/commands/runtime-canary.js +10 -11
- package/dist/commands/runtime-canary.js.map +1 -1
- package/dist/commands/runtime-internalization-queue.d.ts.map +1 -1
- package/dist/commands/runtime-internalization-queue.js +13 -16
- package/dist/commands/runtime-internalization-queue.js.map +1 -1
- package/dist/services/__tests__/runtime-adapter-resolver.test.js +20 -12
- package/dist/services/__tests__/runtime-adapter-resolver.test.js.map +1 -1
- package/dist/services/runtime-adapter-resolver.d.ts +1 -1
- package/dist/services/runtime-adapter-resolver.js +3 -3
- package/dist/services/runtime-adapter-resolver.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/runtime-canary.ts +11 -13
- package/src/commands/runtime-internalization-queue.ts +14 -19
- package/src/services/__tests__/runtime-adapter-resolver.test.ts +20 -12
- package/src/services/runtime-adapter-resolver.ts +3 -3
- package/tests/commands/runtime-canary.test.ts +46 -36
- package/tests/commands/runtime-internalization-queue.test.ts +11 -24
- package/tests/commands/runtime-internalization-run-once.test.ts +14 -6
- package/dist/services/feature-flag-loader.d.ts +0 -6
- package/dist/services/feature-flag-loader.d.ts.map +0 -1
- package/dist/services/feature-flag-loader.js +0 -53
- package/dist/services/feature-flag-loader.js.map +0 -1
- package/src/services/feature-flag-loader.ts +0 -73
- package/tests/services/feature-flag-loader.test.ts +0 -207
|
@@ -62,9 +62,11 @@ vi.mock('@principles/core/principle-tree-ledger', () => ({
|
|
|
62
62
|
loadLedger: vi.fn().mockReturnValue({ tree: { principles: {} } }),
|
|
63
63
|
}));
|
|
64
64
|
|
|
65
|
-
const
|
|
66
|
-
vi.
|
|
67
|
-
|
|
65
|
+
const mockLoadPdConfig = vi.fn();
|
|
66
|
+
const mockComputeFlagsFromLoadResult = vi.fn();
|
|
67
|
+
vi.mock('../pd-config-loader.js', () => ({
|
|
68
|
+
loadPdConfig: mockLoadPdConfig,
|
|
69
|
+
computeFlagsFromLoadResult: mockComputeFlagsFromLoadResult,
|
|
68
70
|
}));
|
|
69
71
|
|
|
70
72
|
const mockResolveRuntimeFromPdConfig = vi.fn();
|
|
@@ -136,8 +138,10 @@ describe('resolveRuntimeAdapterFromConfig (PRI-431)', () => {
|
|
|
136
138
|
// no-op: valid config
|
|
137
139
|
});
|
|
138
140
|
// Default: feature flags have l2_dreamer disabled
|
|
139
|
-
|
|
141
|
+
mockLoadPdConfig.mockReturnValue({ ok: true, effective: {}, source: 'defaults' });
|
|
142
|
+
mockComputeFlagsFromLoadResult.mockReturnValue({
|
|
140
143
|
flags: {},
|
|
144
|
+
enabledChannels: [],
|
|
141
145
|
warnings: [],
|
|
142
146
|
});
|
|
143
147
|
});
|
|
@@ -288,8 +292,9 @@ describe('resolveRuntimeAdapterFromConfig (PRI-431)', () => {
|
|
|
288
292
|
it('returns L2AgentLoopAdapter when runnerKind=dreamer + l2ArtifactReader + l2StateDir + flag enabled', () => {
|
|
289
293
|
const config = makeValidPiAiConfig();
|
|
290
294
|
mockResolveRuntimeFromPdConfig.mockReturnValue(makeResolvedConfig(config));
|
|
291
|
-
|
|
292
|
-
flags: { l2_dreamer: { enabled: true } },
|
|
295
|
+
mockComputeFlagsFromLoadResult.mockReturnValue({
|
|
296
|
+
flags: { l2_dreamer: { id: 'l2_dreamer', enabled: true, category: 'quiet' } },
|
|
297
|
+
enabledChannels: [],
|
|
293
298
|
warnings: [],
|
|
294
299
|
});
|
|
295
300
|
const fakeArtifactReader = { readArtifact: vi.fn() };
|
|
@@ -310,8 +315,9 @@ describe('resolveRuntimeAdapterFromConfig (PRI-431)', () => {
|
|
|
310
315
|
it('falls back to PiAiRuntimeAdapter when l2_dreamer flag is disabled', () => {
|
|
311
316
|
const config = makeValidPiAiConfig();
|
|
312
317
|
mockResolveRuntimeFromPdConfig.mockReturnValue(makeResolvedConfig(config));
|
|
313
|
-
|
|
314
|
-
flags: { l2_dreamer: { enabled: false } },
|
|
318
|
+
mockComputeFlagsFromLoadResult.mockReturnValue({
|
|
319
|
+
flags: { l2_dreamer: { id: 'l2_dreamer', enabled: false, category: 'quiet' } },
|
|
320
|
+
enabledChannels: [],
|
|
315
321
|
warnings: [],
|
|
316
322
|
});
|
|
317
323
|
|
|
@@ -330,8 +336,9 @@ describe('resolveRuntimeAdapterFromConfig (PRI-431)', () => {
|
|
|
330
336
|
it('falls back to PiAiRuntimeAdapter when l2ArtifactReader is missing', () => {
|
|
331
337
|
const config = makeValidPiAiConfig();
|
|
332
338
|
mockResolveRuntimeFromPdConfig.mockReturnValue(makeResolvedConfig(config));
|
|
333
|
-
|
|
334
|
-
flags: { l2_dreamer: { enabled: true } },
|
|
339
|
+
mockComputeFlagsFromLoadResult.mockReturnValue({
|
|
340
|
+
flags: { l2_dreamer: { id: 'l2_dreamer', enabled: true, category: 'quiet' } },
|
|
341
|
+
enabledChannels: [],
|
|
335
342
|
warnings: [],
|
|
336
343
|
});
|
|
337
344
|
|
|
@@ -349,8 +356,9 @@ describe('resolveRuntimeAdapterFromConfig (PRI-431)', () => {
|
|
|
349
356
|
it('falls back to PiAiRuntimeAdapter when runnerKind is not dreamer', () => {
|
|
350
357
|
const config = makeValidPiAiConfig();
|
|
351
358
|
mockResolveRuntimeFromPdConfig.mockReturnValue(makeResolvedConfig(config));
|
|
352
|
-
|
|
353
|
-
flags: { l2_dreamer: { enabled: true } },
|
|
359
|
+
mockComputeFlagsFromLoadResult.mockReturnValue({
|
|
360
|
+
flags: { l2_dreamer: { id: 'l2_dreamer', enabled: true, category: 'quiet' } },
|
|
361
|
+
enabledChannels: [],
|
|
354
362
|
warnings: [],
|
|
355
363
|
});
|
|
356
364
|
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* - CLI-gate concerns (process.exit, telemetry, help text) stay at call sites.
|
|
14
14
|
* This resolver throws structured `ConfigResolutionError` that handlers translate.
|
|
15
15
|
* - Lives in pd-cli/services (NOT core) because it constructs adapters and reads
|
|
16
|
-
*
|
|
16
|
+
* PD config via `loadPdConfig` + `computeFlagsFromLoadResult`, which are pd-cli services.
|
|
17
17
|
*
|
|
18
18
|
* ERR refs:
|
|
19
19
|
* - ERR-001 (no any): all types explicit
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
} from '@principles/core/runtime-v2';
|
|
35
35
|
import type { PDRuntimeAdapter, PdL2ArtifactReader, RuntimeConfig, RuntimeConfigResult } from '@principles/core/runtime-v2';
|
|
36
36
|
import { loadLedger } from '@principles/core/principle-tree-ledger';
|
|
37
|
-
import {
|
|
37
|
+
import { loadPdConfig, computeFlagsFromLoadResult } from './pd-config-loader.js';
|
|
38
38
|
import { resolveRuntimeFromPdConfig } from './resolve-runtime-from-pd-config.js';
|
|
39
39
|
import type { ResolvedRuntimeFromPdConfig } from './resolve-runtime-from-pd-config.js';
|
|
40
40
|
|
|
@@ -255,7 +255,7 @@ export function resolveRuntimeAdapterFromConfig(opts: ResolveAdapterOptions): PD
|
|
|
255
255
|
// dreamer runner, route through the L2 multi-turn agent loop adapter.
|
|
256
256
|
// Other runners (philosopher/scribe/...) stay on L1.
|
|
257
257
|
if (opts.runnerKind === 'dreamer' && opts.l2ArtifactReader && opts.l2StateDir) {
|
|
258
|
-
const effectiveFlags =
|
|
258
|
+
const effectiveFlags = computeFlagsFromLoadResult(loadPdConfig(opts.workspaceDir));
|
|
259
259
|
const l2FlagDef = Object.hasOwn(effectiveFlags.flags, 'l2_dreamer')
|
|
260
260
|
? effectiveFlags.flags.l2_dreamer
|
|
261
261
|
: undefined;
|
|
@@ -46,22 +46,17 @@ vi.mock('@principles/core/runtime-v2', () => ({
|
|
|
46
46
|
resolveOutputLanguage: vi.fn().mockReturnValue({ outputLanguage: 'zh-CN' }),
|
|
47
47
|
}));
|
|
48
48
|
|
|
49
|
-
vi.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
gfi: { id: 'gfi', category: 'quiet', enabled: true, since: '2026-05-24' },
|
|
58
|
-
},
|
|
59
|
-
warnings: [],
|
|
60
|
-
}),
|
|
49
|
+
const { mockLoadPdConfig, mockComputeFlagsFromLoadResult } = vi.hoisted(() => ({
|
|
50
|
+
mockLoadPdConfig: vi.fn(),
|
|
51
|
+
mockComputeFlagsFromLoadResult: vi.fn(),
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
vi.mock('../../src/services/pd-config-loader.js', () => ({
|
|
55
|
+
loadPdConfig: mockLoadPdConfig,
|
|
56
|
+
computeFlagsFromLoadResult: mockComputeFlagsFromLoadResult,
|
|
61
57
|
}));
|
|
62
58
|
|
|
63
59
|
import { runCanaryChecks } from '../../src/commands/runtime-canary.js';
|
|
64
|
-
import { loadEffectiveFeatureFlags } from '../../src/services/feature-flag-loader.js';
|
|
65
60
|
|
|
66
61
|
const WS = '/fake/workspace';
|
|
67
62
|
|
|
@@ -126,6 +121,26 @@ describe('runCanaryChecks', () => {
|
|
|
126
121
|
mockAuditConsistency.mockResolvedValue({ status: 'ok', consumedCount: 0, orphanCandidateCount: 0, missingLedgerCount: 0 });
|
|
127
122
|
mockBuildGfiSnapshot.mockReturnValue(healthyGfiSnapshot());
|
|
128
123
|
mockClassifyGfiHealth.mockReturnValue({ status: 'healthy', reason: '0 active, 0 stale sessions', staleGfiDegradedThreshold: 40 });
|
|
124
|
+
// Default: GFI enabled, no warnings (canonical config loader)
|
|
125
|
+
mockLoadPdConfig.mockReturnValue({
|
|
126
|
+
ok: true,
|
|
127
|
+
effective: {},
|
|
128
|
+
source: 'defaults',
|
|
129
|
+
configPath: `${WS}/.pd/config.yaml`,
|
|
130
|
+
warnings: [],
|
|
131
|
+
legacyFilesDetected: [],
|
|
132
|
+
legacyFileNextActions: [],
|
|
133
|
+
});
|
|
134
|
+
mockComputeFlagsFromLoadResult.mockReturnValue({
|
|
135
|
+
flags: {
|
|
136
|
+
prompt: { id: 'prompt', category: 'core', enabled: true },
|
|
137
|
+
code_tool_hook: { id: 'code_tool_hook', category: 'core', enabled: true },
|
|
138
|
+
defer_archive: { id: 'defer_archive', category: 'core', enabled: true },
|
|
139
|
+
gfi: { id: 'gfi', category: 'quiet', enabled: true },
|
|
140
|
+
},
|
|
141
|
+
enabledChannels: ['prompt', 'code_tool_hook', 'defer_archive'],
|
|
142
|
+
warnings: [],
|
|
143
|
+
});
|
|
129
144
|
});
|
|
130
145
|
|
|
131
146
|
it('returns healthy when all checks are healthy', async () => {
|
|
@@ -275,12 +290,11 @@ describe('runCanaryChecks', () => {
|
|
|
275
290
|
|
|
276
291
|
describe('GFI config warning degraded status', () => {
|
|
277
292
|
it('returns healthy when GFI disabled with no warnings', async () => {
|
|
278
|
-
|
|
279
|
-
source: 'defaults',
|
|
280
|
-
configPath: `${WS}/.pd/feature-flags.yaml`,
|
|
293
|
+
mockComputeFlagsFromLoadResult.mockReturnValue({
|
|
281
294
|
flags: {
|
|
282
|
-
gfi: { id: 'gfi', category: 'quiet', enabled: false
|
|
295
|
+
gfi: { id: 'gfi', category: 'quiet', enabled: false },
|
|
283
296
|
},
|
|
297
|
+
enabledChannels: [],
|
|
284
298
|
warnings: [],
|
|
285
299
|
});
|
|
286
300
|
|
|
@@ -292,13 +306,12 @@ describe('runCanaryChecks', () => {
|
|
|
292
306
|
});
|
|
293
307
|
|
|
294
308
|
it('returns degraded when GFI disabled but config has warnings (malformed YAML)', async () => {
|
|
295
|
-
|
|
296
|
-
source: 'defaults',
|
|
297
|
-
configPath: `${WS}/.pd/feature-flags.yaml`,
|
|
309
|
+
mockComputeFlagsFromLoadResult.mockReturnValue({
|
|
298
310
|
flags: {
|
|
299
|
-
gfi: { id: 'gfi', category: 'quiet', enabled: false
|
|
311
|
+
gfi: { id: 'gfi', category: 'quiet', enabled: false },
|
|
300
312
|
},
|
|
301
|
-
|
|
313
|
+
enabledChannels: [],
|
|
314
|
+
warnings: ['.pd/config.yaml: YAML parse error, using defaults'],
|
|
302
315
|
});
|
|
303
316
|
|
|
304
317
|
const result = await runCanaryChecks(WS);
|
|
@@ -307,16 +320,15 @@ describe('runCanaryChecks', () => {
|
|
|
307
320
|
expect(gfiCheck?.status).toBe('degraded');
|
|
308
321
|
expect(gfiCheck?.summary).toContain('warnings');
|
|
309
322
|
expect(gfiCheck?.details).toBeDefined();
|
|
310
|
-
expect(result.recommendedNextActions.some(a => a.includes('
|
|
323
|
+
expect(result.recommendedNextActions.some(a => a.includes('config.yaml'))).toBe(true);
|
|
311
324
|
});
|
|
312
325
|
|
|
313
326
|
it('returns degraded when GFI disabled but config has malformed override warning', async () => {
|
|
314
|
-
|
|
315
|
-
source: 'workspace_file',
|
|
316
|
-
configPath: `${WS}/.pd/feature-flags.yaml`,
|
|
327
|
+
mockComputeFlagsFromLoadResult.mockReturnValue({
|
|
317
328
|
flags: {
|
|
318
|
-
gfi: { id: 'gfi', category: 'quiet', enabled: false
|
|
329
|
+
gfi: { id: 'gfi', category: 'quiet', enabled: false },
|
|
319
330
|
},
|
|
331
|
+
enabledChannels: [],
|
|
320
332
|
warnings: ["flag 'gfi': malformed override kept default (enabled must be boolean)"],
|
|
321
333
|
});
|
|
322
334
|
|
|
@@ -324,16 +336,15 @@ describe('runCanaryChecks', () => {
|
|
|
324
336
|
const gfiCheck = result.checks.find(c => c.name === 'gfi_snapshot');
|
|
325
337
|
|
|
326
338
|
expect(gfiCheck?.status).toBe('degraded');
|
|
327
|
-
expect(result.recommendedNextActions.some(a => a.includes('
|
|
339
|
+
expect(result.recommendedNextActions.some(a => a.includes('config.yaml') || a.includes('pd runtime features'))).toBe(true);
|
|
328
340
|
});
|
|
329
341
|
|
|
330
342
|
it('runs GFI snapshot when flag enabled with no warnings', async () => {
|
|
331
|
-
|
|
332
|
-
source: 'workspace_file',
|
|
333
|
-
configPath: `${WS}/.pd/feature-flags.yaml`,
|
|
343
|
+
mockComputeFlagsFromLoadResult.mockReturnValue({
|
|
334
344
|
flags: {
|
|
335
|
-
gfi: { id: 'gfi', category: 'quiet', enabled: true
|
|
345
|
+
gfi: { id: 'gfi', category: 'quiet', enabled: true },
|
|
336
346
|
},
|
|
347
|
+
enabledChannels: [],
|
|
337
348
|
warnings: [],
|
|
338
349
|
});
|
|
339
350
|
|
|
@@ -346,12 +357,11 @@ describe('runCanaryChecks', () => {
|
|
|
346
357
|
});
|
|
347
358
|
|
|
348
359
|
it('recommends session lifecycle review for GFI session issues (not config)', async () => {
|
|
349
|
-
|
|
350
|
-
source: 'defaults',
|
|
351
|
-
configPath: `${WS}/.pd/feature-flags.yaml`,
|
|
360
|
+
mockComputeFlagsFromLoadResult.mockReturnValue({
|
|
352
361
|
flags: {
|
|
353
|
-
gfi: { id: 'gfi', category: 'quiet', enabled: true
|
|
362
|
+
gfi: { id: 'gfi', category: 'quiet', enabled: true },
|
|
354
363
|
},
|
|
364
|
+
enabledChannels: [],
|
|
355
365
|
warnings: [],
|
|
356
366
|
});
|
|
357
367
|
mockClassifyGfiHealth.mockReturnValue({
|
|
@@ -24,27 +24,14 @@ vi.mock('@principles/core/runtime-v2', () => ({
|
|
|
24
24
|
resolveOutputLanguage: vi.fn().mockReturnValue({ outputLanguage: 'zh-CN' }),
|
|
25
25
|
}));
|
|
26
26
|
|
|
27
|
-
vi.mock('../../src/services/feature-flag-loader.js', () => ({
|
|
28
|
-
loadEffectiveFeatureFlags: vi.fn().mockReturnValue({
|
|
29
|
-
flags: {
|
|
30
|
-
prompt: { id: 'prompt', enabled: true, category: 'core' },
|
|
31
|
-
code_tool_hook: { id: 'code_tool_hook', enabled: true, category: 'core' },
|
|
32
|
-
defer_archive: { id: 'defer_archive', enabled: true, category: 'core' },
|
|
33
|
-
},
|
|
34
|
-
source: 'defaults',
|
|
35
|
-
configPath: '/fake/workspace/.pd/feature-flags.yaml',
|
|
36
|
-
warnings: [],
|
|
37
|
-
}),
|
|
38
|
-
}));
|
|
39
|
-
|
|
40
27
|
const { mockLoadPdConfig, mockComputeFlagsFromLoadResult } = vi.hoisted(() => ({
|
|
41
|
-
mockLoadPdConfig: vi.fn().mockReturnValue({
|
|
28
|
+
mockLoadPdConfig: vi.fn().mockReturnValue({ ok: true, effective: {}, source: 'defaults' }),
|
|
42
29
|
mockComputeFlagsFromLoadResult: vi.fn().mockReturnValue({
|
|
43
30
|
flags: {
|
|
44
31
|
internalization_auto_consumer: { id: 'internalization_auto_consumer', enabled: true, category: 'quiet' },
|
|
45
32
|
},
|
|
46
|
-
|
|
47
|
-
|
|
33
|
+
enabledChannels: ['prompt', 'code_tool_hook', 'defer_archive'],
|
|
34
|
+
warnings: [],
|
|
48
35
|
}),
|
|
49
36
|
}));
|
|
50
37
|
|
|
@@ -89,8 +76,8 @@ describe('handleRuntimeInternalizationQueue', () => {
|
|
|
89
76
|
flags: {
|
|
90
77
|
internalization_auto_consumer: { id: 'internalization_auto_consumer', enabled: true, category: 'quiet' },
|
|
91
78
|
},
|
|
92
|
-
|
|
93
|
-
|
|
79
|
+
enabledChannels: ['prompt', 'code_tool_hook', 'defer_archive'],
|
|
80
|
+
warnings: [],
|
|
94
81
|
});
|
|
95
82
|
});
|
|
96
83
|
|
|
@@ -308,8 +295,8 @@ describe('handleRuntimeInternalizationQueue', () => {
|
|
|
308
295
|
flags: {
|
|
309
296
|
internalization_auto_consumer: { id: 'internalization_auto_consumer', enabled: true, category: 'quiet' },
|
|
310
297
|
},
|
|
311
|
-
|
|
312
|
-
|
|
298
|
+
enabledChannels: ['prompt', 'code_tool_hook', 'defer_archive'],
|
|
299
|
+
warnings: [],
|
|
313
300
|
});
|
|
314
301
|
|
|
315
302
|
await handleRuntimeInternalizationQueue({ workspace: WS, json: true });
|
|
@@ -332,8 +319,8 @@ describe('handleRuntimeInternalizationQueue', () => {
|
|
|
332
319
|
flags: {
|
|
333
320
|
internalization_auto_consumer: { id: 'internalization_auto_consumer', enabled: false, category: 'quiet' },
|
|
334
321
|
},
|
|
335
|
-
|
|
336
|
-
|
|
322
|
+
enabledChannels: ['prompt', 'code_tool_hook', 'defer_archive'],
|
|
323
|
+
warnings: [],
|
|
337
324
|
});
|
|
338
325
|
|
|
339
326
|
await handleRuntimeInternalizationQueue({ workspace: WS, json: true });
|
|
@@ -379,8 +366,8 @@ describe('handleRuntimeInternalizationQueue', () => {
|
|
|
379
366
|
flags: {
|
|
380
367
|
internalization_auto_consumer: { id: 'internalization_auto_consumer', enabled: false, category: 'quiet' },
|
|
381
368
|
},
|
|
382
|
-
|
|
383
|
-
|
|
369
|
+
enabledChannels: ['prompt', 'code_tool_hook', 'defer_archive'],
|
|
370
|
+
warnings: [],
|
|
384
371
|
});
|
|
385
372
|
|
|
386
373
|
await handleRuntimeInternalizationQueue({ workspace: WS, json: false });
|
|
@@ -41,14 +41,22 @@ vi.mock('../../src/services/resolve-runtime-from-pd-config.js', () => ({
|
|
|
41
41
|
resolveRuntimeFromPdConfig: mockResolveRuntimeFromPdConfig,
|
|
42
42
|
}));
|
|
43
43
|
|
|
44
|
-
// PRI-
|
|
45
|
-
// doesn't call the real
|
|
46
|
-
|
|
47
|
-
vi.
|
|
48
|
-
|
|
44
|
+
// PRI-460: Mock pd-config-loader so the shared resolver's L2 dreamer sub-branch
|
|
45
|
+
// doesn't call the real loadPdConfig (which needs fs access).
|
|
46
|
+
vi.mock('../../src/services/pd-config-loader.js', () => ({
|
|
47
|
+
loadPdConfig: vi.fn().mockReturnValue({
|
|
48
|
+
ok: true,
|
|
49
|
+
effective: {},
|
|
50
|
+
source: 'defaults',
|
|
51
|
+
configPath: '/fake/workspace/.pd/config.yaml',
|
|
52
|
+
warnings: [],
|
|
53
|
+
legacyFilesDetected: [],
|
|
54
|
+
legacyFileNextActions: [],
|
|
55
|
+
}),
|
|
56
|
+
computeFlagsFromLoadResult: vi.fn().mockReturnValue({
|
|
49
57
|
flags: {},
|
|
58
|
+
enabledChannels: [],
|
|
50
59
|
warnings: [],
|
|
51
|
-
configPath: '/fake/workspace/.pd/feature-flags.yaml',
|
|
52
60
|
}),
|
|
53
61
|
}));
|
|
54
62
|
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { EffectiveFeatureFlags } from '@principles/core/runtime-v2';
|
|
2
|
-
export declare const FEATURE_FLAGS_CONFIG_FILENAME = "feature-flags.yaml";
|
|
3
|
-
export declare const FEATURE_FLAGS_CONFIG_DIR = ".pd";
|
|
4
|
-
export declare function getFeatureFlagsConfigPath(workspaceDir: string): string;
|
|
5
|
-
export declare function loadEffectiveFeatureFlags(workspaceDir: string): EffectiveFeatureFlags;
|
|
6
|
-
//# sourceMappingURL=feature-flag-loader.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"feature-flag-loader.d.ts","sourceRoot":"","sources":["../../src/services/feature-flag-loader.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AAEzE,eAAO,MAAM,6BAA6B,uBAAuB,CAAC;AAClE,eAAO,MAAM,wBAAwB,QAAQ,CAAC;AAQ9C,wBAAgB,yBAAyB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAEtE;AAED,wBAAgB,yBAAyB,CAAC,YAAY,EAAE,MAAM,GAAG,qBAAqB,CAkDrF"}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import * as path from 'path';
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
import * as yaml from 'js-yaml';
|
|
4
|
-
import { computeEffectiveFlags, DEFAULT_FEATURE_FLAGS, } from '@principles/core/runtime-v2';
|
|
5
|
-
export const FEATURE_FLAGS_CONFIG_FILENAME = 'feature-flags.yaml';
|
|
6
|
-
export const FEATURE_FLAGS_CONFIG_DIR = '.pd';
|
|
7
|
-
const DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
8
|
-
function isRecord(value) {
|
|
9
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
10
|
-
}
|
|
11
|
-
export function getFeatureFlagsConfigPath(workspaceDir) {
|
|
12
|
-
return path.join(workspaceDir, FEATURE_FLAGS_CONFIG_DIR, FEATURE_FLAGS_CONFIG_FILENAME);
|
|
13
|
-
}
|
|
14
|
-
export function loadEffectiveFeatureFlags(workspaceDir) {
|
|
15
|
-
const configPath = getFeatureFlagsConfigPath(workspaceDir);
|
|
16
|
-
if (!fs.existsSync(configPath)) {
|
|
17
|
-
return computeEffectiveFlags({}, DEFAULT_FEATURE_FLAGS, configPath);
|
|
18
|
-
}
|
|
19
|
-
const raw = fs.readFileSync(configPath, 'utf8');
|
|
20
|
-
let parsed;
|
|
21
|
-
try {
|
|
22
|
-
parsed = yaml.load(raw, { schema: yaml.JSON_SCHEMA });
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
return {
|
|
26
|
-
...computeEffectiveFlags({}, DEFAULT_FEATURE_FLAGS, configPath),
|
|
27
|
-
warnings: ['feature-flags.yaml: YAML parse error, using defaults'],
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
if (parsed === null || parsed === undefined || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
31
|
-
return {
|
|
32
|
-
...computeEffectiveFlags({}, DEFAULT_FEATURE_FLAGS, configPath),
|
|
33
|
-
warnings: ['feature-flags.yaml: expected a mapping, using defaults'],
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
const parsedRecord = Object.create(null);
|
|
37
|
-
const warnings = [];
|
|
38
|
-
for (const key of Object.keys(parsed)) {
|
|
39
|
-
if (DANGEROUS_KEYS.has(key)) {
|
|
40
|
-
warnings.push(`feature-flags.yaml: dangerous key '${key}' rejected`);
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
if (Object.hasOwn(parsed, key) && isRecord(parsed)) {
|
|
44
|
-
parsedRecord[key] = parsed[key];
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
const result = computeEffectiveFlags(parsedRecord, DEFAULT_FEATURE_FLAGS, configPath);
|
|
48
|
-
if (warnings.length > 0) {
|
|
49
|
-
result.warnings = [...warnings, ...result.warnings];
|
|
50
|
-
}
|
|
51
|
-
return result;
|
|
52
|
-
}
|
|
53
|
-
//# sourceMappingURL=feature-flag-loader.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"feature-flag-loader.js","sourceRoot":"","sources":["../../src/services/feature-flag-loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import * as path from 'path';
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
import * as yaml from 'js-yaml';
|
|
4
|
-
import {
|
|
5
|
-
computeEffectiveFlags,
|
|
6
|
-
DEFAULT_FEATURE_FLAGS,
|
|
7
|
-
} from '@principles/core/runtime-v2';
|
|
8
|
-
import type { EffectiveFeatureFlags } from '@principles/core/runtime-v2';
|
|
9
|
-
|
|
10
|
-
export const FEATURE_FLAGS_CONFIG_FILENAME = 'feature-flags.yaml';
|
|
11
|
-
export const FEATURE_FLAGS_CONFIG_DIR = '.pd';
|
|
12
|
-
|
|
13
|
-
const DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
14
|
-
|
|
15
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
16
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function getFeatureFlagsConfigPath(workspaceDir: string): string {
|
|
20
|
-
return path.join(workspaceDir, FEATURE_FLAGS_CONFIG_DIR, FEATURE_FLAGS_CONFIG_FILENAME);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function loadEffectiveFeatureFlags(workspaceDir: string): EffectiveFeatureFlags {
|
|
24
|
-
const configPath = getFeatureFlagsConfigPath(workspaceDir);
|
|
25
|
-
|
|
26
|
-
if (!fs.existsSync(configPath)) {
|
|
27
|
-
return computeEffectiveFlags({}, DEFAULT_FEATURE_FLAGS, configPath);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const raw = fs.readFileSync(configPath, 'utf8');
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
let parsed: unknown;
|
|
34
|
-
try {
|
|
35
|
-
parsed = yaml.load(raw, { schema: yaml.JSON_SCHEMA });
|
|
36
|
-
} catch {
|
|
37
|
-
return {
|
|
38
|
-
...computeEffectiveFlags({}, DEFAULT_FEATURE_FLAGS, configPath),
|
|
39
|
-
warnings: ['feature-flags.yaml: YAML parse error, using defaults'],
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (parsed === null || parsed === undefined || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
44
|
-
return {
|
|
45
|
-
...computeEffectiveFlags({}, DEFAULT_FEATURE_FLAGS, configPath),
|
|
46
|
-
warnings: ['feature-flags.yaml: expected a mapping, using defaults'],
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const parsedRecord: Record<string, unknown> = Object.create(null);
|
|
51
|
-
const warnings: string[] = [];
|
|
52
|
-
for (const key of Object.keys(parsed)) {
|
|
53
|
-
if (DANGEROUS_KEYS.has(key)) {
|
|
54
|
-
warnings.push(`feature-flags.yaml: dangerous key '${key}' rejected`);
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
if (Object.hasOwn(parsed, key) && isRecord(parsed)) {
|
|
58
|
-
parsedRecord[key] = parsed[key];
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const result = computeEffectiveFlags(
|
|
63
|
-
parsedRecord,
|
|
64
|
-
DEFAULT_FEATURE_FLAGS,
|
|
65
|
-
configPath,
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
if (warnings.length > 0) {
|
|
69
|
-
result.warnings = [...warnings, ...result.warnings];
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return result;
|
|
73
|
-
}
|