@clearlydev/nx-archweave 0.1.0 → 0.2.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/README.md +2 -2
- package/dist/eslint/fsd-boundaries.d.ts +27 -10
- package/dist/eslint/fsd-policy.d.ts +38 -0
- package/dist/eslint/index.d.ts +10 -9
- package/dist/eslint/index.js +227 -34
- package/dist/eslint/slice-groups.d.ts +12 -0
- package/dist/generators/add-agent-rules/generator.js +1175 -23
- package/dist/generators/add-backend/generator.js +1127 -75
- package/dist/generators/add-fsd-lite/generator.js +1274 -96
- package/dist/generators/add-integration/generator.js +4 -35
- package/dist/generators/add-review/files/dependabot.yml +7 -0
- package/dist/generators/add-review/generator.js +1100 -46
- package/dist/generators/doctor/generator.js +784 -131
- package/dist/generators/entity/generator.js +251 -32
- package/dist/generators/feature/generator.js +251 -32
- package/dist/generators/init/generator.js +1955 -549
- package/dist/generators/install-integration/generator.js +5 -37
- package/dist/generators/page/generator.js +251 -32
- package/dist/generators/record-integration/generator.js +5 -36
- package/dist/generators/shared/generator.js +251 -32
- package/dist/generators/widget/generator.js +251 -32
- package/dist/index.js +4 -35
- package/package.json +5 -3
- package/src/generators/add-agent-rules/schema.json +5 -0
- package/src/generators/add-fsd-lite/schema.json +2 -2
- package/src/generators/add-integration/schema.json +1 -1
- package/src/generators/entity/schema.json +11 -1
- package/src/generators/feature/schema.json +11 -1
- package/src/generators/init/schema.json +13 -2
- package/src/generators/page/schema.json +15 -3
- package/src/generators/shared/schema.json +7 -1
- package/src/generators/widget/schema.json +11 -1
- package/dist/eslint/fsd-boundaries.js +0 -144
- package/dist/eslint/tsconfig-aliases.js +0 -114
- package/dist/generators/add-agent-rules/render.js +0 -46
- package/dist/generators/add-backend/render.js +0 -102
- package/dist/generators/add-fsd-lite/render.js +0 -72
- package/dist/generators/add-github-templates/render.js +0 -26
- package/dist/generators/add-integration/render.js +0 -34
- package/dist/generators/add-review/render.js +0 -96
- package/dist/generators/allow-builds/render.js +0 -82
- package/dist/generators/doctor/integration-report.js +0 -70
- package/dist/generators/doctor/render.js +0 -10
- package/dist/generators/init/render.js +0 -125
- package/dist/generators/init/validation.js +0 -74
- package/dist/generators/install-integration/render.js +0 -128
- package/dist/generators/record-integration/render.js +0 -31
- package/dist/generators/shared/dry-run.js +0 -12
- package/dist/generators/shared/frontend-app.js +0 -57
- package/dist/generators/shared/project-type.js +0 -39
- package/dist/generators/shared/record-pipeline.js +0 -34
- package/dist/generators/shared/run-nx.js +0 -24
- package/dist/generators/shared/template-files.js +0 -38
- package/dist/generators/shared/toolkit-config-file.js +0 -110
- package/dist/generators/slice/core.js +0 -185
- package/dist/generators/slice/render.js +0 -70
- package/dist/lib/nx-plugin.js +0 -7
- package/dist/runtime/integrations-composition-root.js +0 -20
- package/dist/runtime/node-command-runner.js +0 -81
|
@@ -243,6 +243,20 @@ function buildReviewConfigSnippet(input) {
|
|
|
243
243
|
};
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
+
// ../template-engine/dist/review/action-pins.js
|
|
247
|
+
var ACTION_PINS = {
|
|
248
|
+
"actions/checkout": "actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0",
|
|
249
|
+
"actions/setup-node": "actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0",
|
|
250
|
+
"pnpm/action-setup": "pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0",
|
|
251
|
+
"oven-sh/setup-bun": "oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0",
|
|
252
|
+
"anthropics/claude-code-action": "anthropics/claude-code-action@6b6870e649b5ff408753befeaee5fc7989d4e6a9 # v1.0.258",
|
|
253
|
+
"google-github-actions/run-gemini-cli": "google-github-actions/run-gemini-cli@f77273f4c914e4bf38440cf36a0369cb64a37489 # v0.1.22",
|
|
254
|
+
"openai/codex-action": "openai/codex-action@bdf19a4a223ec2549a3e2274a0cf61556bc07675 # v1.13"
|
|
255
|
+
};
|
|
256
|
+
function pinnedUses(action) {
|
|
257
|
+
return ACTION_PINS[action];
|
|
258
|
+
}
|
|
259
|
+
|
|
246
260
|
// ../template-engine/dist/review/workflow-skeleton.js
|
|
247
261
|
function indentBlock(text, spaces) {
|
|
248
262
|
const pad = " ".repeat(spaces);
|
|
@@ -261,6 +275,17 @@ function renderConcurrencyBlock() {
|
|
|
261
275
|
function renderPermissionsBlock() {
|
|
262
276
|
return ["permissions:", " contents: read", " pull-requests: write", " id-token: write"].join("\n");
|
|
263
277
|
}
|
|
278
|
+
function renderCheckoutStep(gateCondition, extraWith = []) {
|
|
279
|
+
return [
|
|
280
|
+
"- name: Checkout repository",
|
|
281
|
+
` if: ${gateCondition}`,
|
|
282
|
+
` uses: ${pinnedUses("actions/checkout")}`,
|
|
283
|
+
" with:",
|
|
284
|
+
...extraWith.map((line) => ` ${line}`),
|
|
285
|
+
" fetch-depth: 1",
|
|
286
|
+
" persist-credentials: false"
|
|
287
|
+
].join("\n");
|
|
288
|
+
}
|
|
264
289
|
function renderSecretGateStep(secretName) {
|
|
265
290
|
return [
|
|
266
291
|
"- name: Check review secret",
|
|
@@ -284,7 +309,7 @@ function renderRoundGateStep(maxRounds) {
|
|
|
284
309
|
" env:",
|
|
285
310
|
" GH_TOKEN: ${{ github.token }}",
|
|
286
311
|
" run: |",
|
|
287
|
-
` counts=$(gh api "repos/\${{ github.repository }}/issues/\${{ github.event.pull_request.number }}/comments" --paginate --jq '[([.[] | select(.body | contains("${ROUND_MARKER}"))] | length), ([.[].id] | max // 0)]' | jq -s -r '[(map(.[0]) | add // 0), (map(.[1]) | max // 0)] | @tsv')`,
|
|
312
|
+
` counts=$(gh api "repos/\${{ github.repository }}/issues/\${{ github.event.pull_request.number }}/comments" --paginate --jq '[([.[] | select(.user.type == "Bot") | select(.body | contains("${ROUND_MARKER}"))] | length), ([.[].id] | max // 0)]' | jq -s -r '[(map(.[0]) | add // 0), (map(.[1]) | max // 0)] | @tsv')`,
|
|
288
313
|
` rounds=$(printf '%s' "$counts" | cut -f1)`,
|
|
289
314
|
` echo "last-comment-id=$(printf '%s' "$counts" | cut -f2)" >> "$GITHUB_OUTPUT"`,
|
|
290
315
|
` if [ "$rounds" -ge ${maxRounds} ]; then`,
|
|
@@ -316,8 +341,7 @@ function renderVerdictGateStep(maxRounds, locale) {
|
|
|
316
341
|
].join("\n");
|
|
317
342
|
}
|
|
318
343
|
var SETUP_NODE_VERSION = "22";
|
|
319
|
-
var
|
|
320
|
-
var SETUP_BUN_ACTION = "oven-sh/setup-bun@v2";
|
|
344
|
+
var FALLBACK_PNPM_VERSION = "10";
|
|
321
345
|
var INSTALL_LINES = {
|
|
322
346
|
pnpm: [" pnpm install --frozen-lockfile"],
|
|
323
347
|
npm: [" npm ci"],
|
|
@@ -327,19 +351,39 @@ var INSTALL_LINES = {
|
|
|
327
351
|
],
|
|
328
352
|
bun: [" bun install --frozen-lockfile"]
|
|
329
353
|
};
|
|
354
|
+
function renderPnpmVersionStep(gate) {
|
|
355
|
+
return [
|
|
356
|
+
"- name: Detect pnpm version",
|
|
357
|
+
" id: pnpm-version",
|
|
358
|
+
gate,
|
|
359
|
+
" run: |",
|
|
360
|
+
` if [ -n "$(jq -r '.packageManager // empty' package.json)" ]; then`,
|
|
361
|
+
' echo "version=" >> "$GITHUB_OUTPUT"',
|
|
362
|
+
" else",
|
|
363
|
+
` echo "version=${FALLBACK_PNPM_VERSION}" >> "$GITHUB_OUTPUT"`,
|
|
364
|
+
" fi"
|
|
365
|
+
].join("\n");
|
|
366
|
+
}
|
|
330
367
|
function renderWorkspaceSetupSteps(packageManager) {
|
|
331
368
|
const gate = " if: steps.secret-gate.outputs.enabled == 'true' && steps.round-gate.outputs.allowed == 'true'";
|
|
332
369
|
const steps = [];
|
|
333
370
|
if (packageManager === "pnpm") {
|
|
334
|
-
steps.push(
|
|
371
|
+
steps.push(renderPnpmVersionStep(gate));
|
|
372
|
+
steps.push([
|
|
373
|
+
"- name: Set up pnpm",
|
|
374
|
+
gate,
|
|
375
|
+
` uses: ${pinnedUses("pnpm/action-setup")}`,
|
|
376
|
+
" with:",
|
|
377
|
+
" version: ${{ steps.pnpm-version.outputs.version }}"
|
|
378
|
+
].join("\n"));
|
|
335
379
|
}
|
|
336
380
|
if (packageManager === "bun") {
|
|
337
|
-
steps.push(["- name: Set up bun", gate, ` uses: ${
|
|
381
|
+
steps.push(["- name: Set up bun", gate, ` uses: ${pinnedUses("oven-sh/setup-bun")}`].join("\n"));
|
|
338
382
|
}
|
|
339
383
|
steps.push([
|
|
340
384
|
"- name: Set up Node",
|
|
341
385
|
gate,
|
|
342
|
-
|
|
386
|
+
` uses: ${pinnedUses("actions/setup-node")}`,
|
|
343
387
|
" with:",
|
|
344
388
|
` node-version: ${SETUP_NODE_VERSION}`,
|
|
345
389
|
// yarn: no setup-node cache — it would query the runner's global yarn 1 even in a
|
|
@@ -391,17 +435,11 @@ function buildWorkflowYaml(input) {
|
|
|
391
435
|
const steps = [
|
|
392
436
|
renderSecretGateStep(SECRET_NAME),
|
|
393
437
|
renderRoundGateStep(input.maxRounds),
|
|
394
|
-
|
|
395
|
-
"- name: Checkout repository",
|
|
396
|
-
" if: steps.secret-gate.outputs.enabled == 'true' && steps.round-gate.outputs.allowed == 'true'",
|
|
397
|
-
" uses: actions/checkout@v4",
|
|
398
|
-
" with:",
|
|
399
|
-
" fetch-depth: 1"
|
|
400
|
-
].join("\n"),
|
|
438
|
+
renderCheckoutStep("steps.secret-gate.outputs.enabled == 'true' && steps.round-gate.outputs.allowed == 'true'"),
|
|
401
439
|
[
|
|
402
440
|
"- name: Run Claude review",
|
|
403
441
|
" if: steps.secret-gate.outputs.enabled == 'true' && steps.round-gate.outputs.allowed == 'true'",
|
|
404
|
-
|
|
442
|
+
` uses: ${pinnedUses("anthropics/claude-code-action")}`,
|
|
405
443
|
" with:",
|
|
406
444
|
` claude_code_oauth_token: \${{ secrets.${SECRET_NAME} }}`,
|
|
407
445
|
" prompt: |",
|
|
@@ -471,13 +509,7 @@ function buildWorkflowYaml2(input) {
|
|
|
471
509
|
const steps = [
|
|
472
510
|
renderSecretGateStep(SECRET_NAME2),
|
|
473
511
|
renderRoundGateStep(input.maxRounds),
|
|
474
|
-
|
|
475
|
-
"- name: Checkout repository",
|
|
476
|
-
` if: ${gateCondition}`,
|
|
477
|
-
" uses: actions/checkout@v4",
|
|
478
|
-
" with:",
|
|
479
|
-
" fetch-depth: 1"
|
|
480
|
-
].join("\n"),
|
|
512
|
+
renderCheckoutStep(gateCondition),
|
|
481
513
|
[
|
|
482
514
|
"- name: Install cursor-agent CLI",
|
|
483
515
|
` if: ${gateCondition}`,
|
|
@@ -557,17 +589,11 @@ function buildWorkflowYaml3(input) {
|
|
|
557
589
|
const steps = [
|
|
558
590
|
renderSecretGateStep(SECRET_NAME3),
|
|
559
591
|
renderRoundGateStep(input.maxRounds),
|
|
560
|
-
|
|
561
|
-
"- name: Checkout repository",
|
|
562
|
-
" if: steps.secret-gate.outputs.enabled == 'true' && steps.round-gate.outputs.allowed == 'true'",
|
|
563
|
-
" uses: actions/checkout@v4",
|
|
564
|
-
" with:",
|
|
565
|
-
" fetch-depth: 1"
|
|
566
|
-
].join("\n"),
|
|
592
|
+
renderCheckoutStep("steps.secret-gate.outputs.enabled == 'true' && steps.round-gate.outputs.allowed == 'true'"),
|
|
567
593
|
[
|
|
568
594
|
"- name: Run Gemini review",
|
|
569
595
|
" if: steps.secret-gate.outputs.enabled == 'true' && steps.round-gate.outputs.allowed == 'true'",
|
|
570
|
-
|
|
596
|
+
` uses: ${pinnedUses("google-github-actions/run-gemini-cli")}`,
|
|
571
597
|
" env:",
|
|
572
598
|
" GITHUB_TOKEN: ${{ github.token }}",
|
|
573
599
|
" with:",
|
|
@@ -636,15 +662,7 @@ function buildWorkflowYaml4(input) {
|
|
|
636
662
|
const steps = [
|
|
637
663
|
renderSecretGateStep(SECRET_NAME4),
|
|
638
664
|
renderRoundGateStep(input.maxRounds),
|
|
639
|
-
[
|
|
640
|
-
"- name: Checkout repository",
|
|
641
|
-
` if: ${gateCondition}`,
|
|
642
|
-
" uses: actions/checkout@v4",
|
|
643
|
-
" with:",
|
|
644
|
-
" ref: refs/pull/${{ github.event.pull_request.number }}/merge",
|
|
645
|
-
" fetch-depth: 1",
|
|
646
|
-
" persist-credentials: false"
|
|
647
|
-
].join("\n"),
|
|
665
|
+
renderCheckoutStep(gateCondition, ["ref: refs/pull/${{ github.event.pull_request.number }}/merge"]),
|
|
648
666
|
[
|
|
649
667
|
"- name: Fetch base and head refs for the PR",
|
|
650
668
|
` if: ${gateCondition}`,
|
|
@@ -662,7 +680,7 @@ function buildWorkflowYaml4(input) {
|
|
|
662
680
|
"- name: Run Codex review",
|
|
663
681
|
" id: codex-review",
|
|
664
682
|
` if: ${gateCondition}`,
|
|
665
|
-
|
|
683
|
+
` uses: ${pinnedUses("openai/codex-action")}`,
|
|
666
684
|
" with:",
|
|
667
685
|
` openai-api-key: \${{ secrets.${SECRET_NAME4} }}`,
|
|
668
686
|
' permission-profile: ":read-only"',
|
|
@@ -737,13 +755,26 @@ function judgeReviewFileConflict(existing, generated) {
|
|
|
737
755
|
}
|
|
738
756
|
|
|
739
757
|
// ../template-engine/dist/backend/eslint-boundaries.js
|
|
740
|
-
var
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
758
|
+
var DEFAULT_BOUNDARY_TAGS = { frontend: "scope:frontend", backend: "scope:backend", shared: "scope:shared" };
|
|
759
|
+
function readBoundaryTags(config) {
|
|
760
|
+
const boundaries = config?.["boundaries"];
|
|
761
|
+
const tags = typeof boundaries === "object" && boundaries !== null ? boundaries["tags"] : void 0;
|
|
762
|
+
const pick = (role) => {
|
|
763
|
+
const value = typeof tags === "object" && tags !== null ? tags[role] : void 0;
|
|
764
|
+
return typeof value === "string" && value.trim() !== "" ? value : DEFAULT_BOUNDARY_TAGS[role];
|
|
765
|
+
};
|
|
766
|
+
return { frontend: pick("frontend"), backend: pick("backend"), shared: pick("shared") };
|
|
767
|
+
}
|
|
768
|
+
function scopeConstraints(tags = DEFAULT_BOUNDARY_TAGS) {
|
|
769
|
+
return [
|
|
770
|
+
{ sourceTag: tags.frontend, notDependOnLibsWithTags: [tags.backend] },
|
|
771
|
+
{ sourceTag: tags.backend, notDependOnLibsWithTags: [tags.frontend] },
|
|
772
|
+
{ sourceTag: tags.shared, notDependOnLibsWithTags: [tags.frontend, tags.backend] }
|
|
773
|
+
];
|
|
774
|
+
}
|
|
775
|
+
var SCOPE_CONSTRAINTS = scopeConstraints();
|
|
745
776
|
var ESLINT_CONFIG_CANDIDATES = ["eslint.config.mjs", "eslint.config.js", "eslint.config.cjs"];
|
|
746
|
-
var
|
|
777
|
+
var appliedMarker = (tags) => `sourceTag: '${tags.frontend}'`;
|
|
747
778
|
var ALLOW_ALL_BLOCK = /depConstraints:\s*\[\s*\{\s*sourceTag:\s*'\*',\s*onlyDependOnLibsWithTags:\s*\[\s*'\*'\s*\],?\s*\},?\s*\]/g;
|
|
748
779
|
function maskComments(text) {
|
|
749
780
|
const out = text.split("");
|
|
@@ -796,17 +827,17 @@ function findAllowAllMatches(masked) {
|
|
|
796
827
|
}
|
|
797
828
|
return matches;
|
|
798
829
|
}
|
|
799
|
-
function classifyBoundaryConstraints(configText) {
|
|
830
|
+
function classifyBoundaryConstraints(configText, tags = DEFAULT_BOUNDARY_TAGS) {
|
|
800
831
|
const masked = maskComments(configText);
|
|
801
|
-
if (masked.includes(
|
|
832
|
+
if (masked.includes(appliedMarker(tags)))
|
|
802
833
|
return "enforced";
|
|
803
834
|
if (findAllowAllMatches(masked).length > 0)
|
|
804
835
|
return "allow-all";
|
|
805
836
|
return "unknown";
|
|
806
837
|
}
|
|
807
|
-
function renderConstraintsBlock(indent, eol) {
|
|
808
|
-
const renderList = (
|
|
809
|
-
const inner =
|
|
838
|
+
function renderConstraintsBlock(indent, eol, tags) {
|
|
839
|
+
const renderList = (list) => list.map((t) => `'${t}'`).join(", ");
|
|
840
|
+
const inner = scopeConstraints(tags).map((c) => {
|
|
810
841
|
const field = c.notDependOnLibsWithTags ? `notDependOnLibsWithTags: [${renderList(c.notDependOnLibsWithTags)}]` : `onlyDependOnLibsWithTags: [${renderList(c.onlyDependOnLibsWithTags ?? [])}]`;
|
|
811
842
|
return [`${indent} {`, `${indent} sourceTag: '${c.sourceTag}',`, `${indent} ${field},`, `${indent} },`].join(eol);
|
|
812
843
|
}).join(eol);
|
|
@@ -824,8 +855,8 @@ function renderConstraintsBlock(indent, eol) {
|
|
|
824
855
|
`${indent}]`
|
|
825
856
|
].join(eol);
|
|
826
857
|
}
|
|
827
|
-
function applyBoundaryConstraints(configText) {
|
|
828
|
-
const classification = classifyBoundaryConstraints(configText);
|
|
858
|
+
function applyBoundaryConstraints(configText, tags = DEFAULT_BOUNDARY_TAGS) {
|
|
859
|
+
const classification = classifyBoundaryConstraints(configText, tags);
|
|
829
860
|
if (classification === "enforced")
|
|
830
861
|
return { status: "already-applied" };
|
|
831
862
|
if (classification === "unknown")
|
|
@@ -839,17 +870,17 @@ function applyBoundaryConstraints(configText) {
|
|
|
839
870
|
const eol = configText.includes("\r\n") ? "\r\n" : "\n";
|
|
840
871
|
const lineStart = configText.lastIndexOf("\n", match.index) + 1;
|
|
841
872
|
const indent = /^[ \t]*/.exec(configText.slice(lineStart, match.index))?.[0] ?? "";
|
|
842
|
-
const replacement = renderConstraintsBlock(indent, eol);
|
|
873
|
+
const replacement = renderConstraintsBlock(indent, eol, tags);
|
|
843
874
|
const text = configText.slice(0, match.index) + replacement + configText.slice(match.index + match.length);
|
|
844
875
|
return { status: "applied", text };
|
|
845
876
|
}
|
|
846
|
-
function buildBoundarySnippet() {
|
|
877
|
+
function buildBoundarySnippet(tags = DEFAULT_BOUNDARY_TAGS) {
|
|
847
878
|
return [
|
|
848
879
|
"// nx-archweave project boundaries \u2014 merge these entries into the depConstraints",
|
|
849
880
|
"// array of the '@nx/enforce-module-boundaries' rule in your root ESLint config.",
|
|
850
881
|
"// They ride on top of your existing entries: constraints apply conjunctively,",
|
|
851
882
|
"// so the allow-all baseline can stay.",
|
|
852
|
-
renderConstraintsBlock("", "\n")
|
|
883
|
+
renderConstraintsBlock("", "\n", tags)
|
|
853
884
|
].join("\n");
|
|
854
885
|
}
|
|
855
886
|
|
|
@@ -1140,25 +1171,53 @@ var AGENT_RULE_SECTIONS = {
|
|
|
1140
1171
|
ko: KO_SECTIONS
|
|
1141
1172
|
};
|
|
1142
1173
|
|
|
1174
|
+
// ../templates/dist/agents/fsd-policy-notes.js
|
|
1175
|
+
var NOTES = {
|
|
1176
|
+
en: {
|
|
1177
|
+
heading: "This workspace also configures:",
|
|
1178
|
+
groups: "- A folder that only groups slices (`features/mypage/{order,claims}`) holds no code and no `index.ts`; each slice in it is separate, and slices in one group never import each other either. Create one with `--group=<name>`.",
|
|
1179
|
+
component: "- `shared` exposes each component through its own `index.ts` (`shared/ui/button`); import that, not the files inside it.",
|
|
1180
|
+
file: "- Inside `shared`, files may be imported directly (`shared/lib/format/currency`); `shared` still never imports a layer above it.",
|
|
1181
|
+
tests: "- Test files (`*.spec.ts`, `*.test.ts`, `__tests__/`) may import another slice's internals; upward imports stay forbidden there too."
|
|
1182
|
+
},
|
|
1183
|
+
ko: {
|
|
1184
|
+
heading: "\uC774 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB294 \uB2E4\uC74C\uB3C4 \uC124\uC815\uD588\uB2E4:",
|
|
1185
|
+
groups: "- \uC2AC\uB77C\uC774\uC2A4\uB97C \uBB36\uAE30\uB9CC \uD558\uB294 \uD3F4\uB354(`features/mypage/{order,claims}`)\uC5D0\uB294 \uCF54\uB4DC\uB3C4 `index.ts`\uB3C4 \uB450\uC9C0 \uC54A\uB294\uB2E4. \uADF8 \uC548\uC758 \uC2AC\uB77C\uC774\uC2A4\uB294 \uAC01\uAC01 \uB3C5\uB9BD\uC774\uB77C, \uAC19\uC740 \uADF8\uB8F9 \uC548\uC5D0\uC11C\uB3C4 \uC11C\uB85C import\uD558\uC9C0 \uC54A\uB294\uB2E4. \uADF8\uB8F9 \uC548\uC5D0 \uB9CC\uB4E4 \uB54C\uB294 `--group=<\uC774\uB984>`\uC744 \uC4F4\uB2E4.",
|
|
1186
|
+
component: "- `shared`\uB294 \uCEF4\uD3EC\uB10C\uD2B8\uB9C8\uB2E4 \uC790\uAE30 `index.ts`\uB85C \uACF5\uAC1C\uD55C\uB2E4(`shared/ui/button`). \uADF8 index\uB97C import\uD558\uACE0, \uC548\uCABD \uD30C\uC77C\uC5D0\uB294 \uB2FF\uC9C0 \uC54A\uB294\uB2E4.",
|
|
1187
|
+
file: "- `shared` \uC548\uC758 \uD30C\uC77C\uC740 \uC9C1\uC811 import\uD574\uB3C4 \uB41C\uB2E4(`shared/lib/format/currency`). \uB2E4\uB9CC `shared`\uB294 \uC5EC\uC804\uD788 \uC704 \uB808\uC774\uC5B4\uB97C import\uD558\uC9C0 \uC54A\uB294\uB2E4.",
|
|
1188
|
+
tests: "- \uD14C\uC2A4\uD2B8 \uD30C\uC77C(`*.spec.ts`, `*.test.ts`, `__tests__/`)\uC740 \uB2E4\uB978 \uC2AC\uB77C\uC774\uC2A4\uC758 \uB0B4\uBD80 \uD30C\uC77C\uC744 import\uD574\uB3C4 \uB41C\uB2E4. \uC704 \uB808\uC774\uC5B4 import\uB294 \uD14C\uC2A4\uD2B8\uC5D0\uC11C\uB3C4 \uAE08\uC9C0\uB2E4."
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1191
|
+
function buildFsdPolicyNotes(locale, notes) {
|
|
1192
|
+
const text = NOTES[locale];
|
|
1193
|
+
const lines = [
|
|
1194
|
+
...notes.sliceGroups ? [text.groups] : [],
|
|
1195
|
+
...notes.sharedPublicApi === "component" ? [text.component] : notes.sharedPublicApi === "file" ? [text.file] : [],
|
|
1196
|
+
...notes.testFiles ? [text.tests] : []
|
|
1197
|
+
];
|
|
1198
|
+
return lines.length === 0 ? "" : [text.heading, ...lines].join("\n");
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1143
1201
|
// ../templates/dist/fsd/fsd-eslint.js
|
|
1144
1202
|
var FSD_ESLINT_MODULE = "@clearlydev/nx-archweave/eslint";
|
|
1145
1203
|
function buildFsdEslintImportLine() {
|
|
1146
1204
|
return `import { fsdBoundaries } from '${FSD_ESLINT_MODULE}';`;
|
|
1147
1205
|
}
|
|
1148
|
-
function buildFsdEslintEntry(severity) {
|
|
1149
|
-
|
|
1206
|
+
function buildFsdEslintEntry(severity, root = "src", framework = {}) {
|
|
1207
|
+
const list = (key, values) => values === void 0 || values.length === 0 ? "" : `, ${key}: [${values.map((value) => `'${value}'`).join(", ")}]`;
|
|
1208
|
+
return `...fsdBoundaries({ severity: '${severity}', cwd: import.meta.dirname, root: '${root}'${list("platformSuffixes", framework.platformSuffixes)}${list("routerDirs", framework.routerDirs)} }),`;
|
|
1150
1209
|
}
|
|
1151
|
-
function buildFsdEslintSnippet({ severity, locale }) {
|
|
1210
|
+
function buildFsdEslintSnippet({ severity, locale, root, framework }) {
|
|
1152
1211
|
const lines = locale === "ko" ? [
|
|
1153
1212
|
"// nx-archweave FSD \uB808\uC774\uC5B4 \uADDC\uCE59 \u2014 \uD504\uB860\uD2B8\uC5D4\uB4DC \uC571\uC758 eslint.config.mjs\uC5D0 \uC9C1\uC811 \uB123\uC73C\uC138\uC694:",
|
|
1154
1213
|
"// 1) \uB9C8\uC9C0\uB9C9 import \uB4A4\uC5D0 \uC544\uB798 import\uB97C,",
|
|
1155
|
-
"// 2) export default [...] \uBC30\uC5F4\uC758 \uB9C8\uC9C0\uB9C9 \uD56D\uBAA9\uC73C\uB85C \uC544\uB798 spread\uB97C \uCD94\uAC00\uD569\uB2C8\uB2E4."
|
|
1214
|
+
"// 2) \uC124\uC815 \uBC30\uC5F4(export default [...], \uB610\uB294 export default \uD558\uB294 \uBCC0\uC218\uC758 \uBC30\uC5F4)\uC758 \uB9C8\uC9C0\uB9C9 \uD56D\uBAA9\uC73C\uB85C \uC544\uB798 spread\uB97C \uCD94\uAC00\uD569\uB2C8\uB2E4."
|
|
1156
1215
|
] : [
|
|
1157
1216
|
"// nx-archweave FSD layer rules \u2014 add these to the frontend app's eslint.config.mjs yourself:",
|
|
1158
1217
|
"// 1) the import after the last import,",
|
|
1159
|
-
"// 2) the spread as the last element of the export default [...] array."
|
|
1218
|
+
"// 2) the spread as the last element of the config array (export default [...], or the array your config exports by name)."
|
|
1160
1219
|
];
|
|
1161
|
-
return [...lines, buildFsdEslintImportLine(), buildFsdEslintEntry(severity)].join("\n");
|
|
1220
|
+
return [...lines, buildFsdEslintImportLine(), buildFsdEslintEntry(severity, root, framework)].join("\n");
|
|
1162
1221
|
}
|
|
1163
1222
|
var LAYER_GUIDE_EN = {
|
|
1164
1223
|
app: "composition root: providers, routing, global styles (the framework entry point lives here)",
|
|
@@ -1204,7 +1263,7 @@ var FSD_CHECK_TARGET = "fsd-check";
|
|
|
1204
1263
|
function buildFsdCheckTarget(sourceRootName = "src") {
|
|
1205
1264
|
return { command: `steiger ${sourceRootName}`, options: { cwd: "{projectRoot}" } };
|
|
1206
1265
|
}
|
|
1207
|
-
function buildSteigerConfig({
|
|
1266
|
+
function buildSteigerConfig({ renamedLayerDirs, locale }) {
|
|
1208
1267
|
const ko = locale === "ko";
|
|
1209
1268
|
const lines = [
|
|
1210
1269
|
"import { defineConfig } from 'steiger';",
|
|
@@ -1217,8 +1276,8 @@ function buildSteigerConfig({ frontend, locale }) {
|
|
|
1217
1276
|
" rules: {",
|
|
1218
1277
|
ko ? " // \uC544\uBB34\uB3C4 \uCC38\uC870\uD558\uC9C0 \uC54A\uB294 \uC2AC\uB77C\uC774\uC2A4 = \uBC29\uAE08 \uC0DD\uC131\uD55C \uC2AC\uB77C\uC774\uC2A4. \uACBD\uACE0\uB85C \uBCF4\uC774\uB418 \uAC80\uC0AC\uB97C \uB9C9\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4." : " // a slice nothing references yet is every freshly generated slice: visible, not blocking",
|
|
1219
1278
|
" 'fsd/insignificant-slice': 'warn',",
|
|
1220
|
-
...
|
|
1221
|
-
ko ?
|
|
1279
|
+
...renamedLayerDirs.length > 0 ? [
|
|
1280
|
+
ko ? ` // ${renamedLayerDirs.join(", ")}: \uD504\uB808\uC784\uC6CC\uD06C \uB77C\uC6B0\uD130\uC640 \uACB9\uCE58\uC9C0 \uC54A\uAC8C \uC774\uB984\uC744 \uBC14\uAFBC \uB808\uC774\uC5B4 (FSD \uACF5\uC2DD \uBA85\uBA85) \u2014 steiger 0.7\uC740 \uC544\uC9C1 \uC624\uD0C0\uB85C \uBD05\uB2C8\uB2E4.` : ` // ${renamedLayerDirs.join(", ")}: layers renamed out of the framework router's way (FSD's own naming) \u2014 steiger 0.7 still reads them as typos`,
|
|
1222
1281
|
" 'fsd/typo-in-layer-name': 'off',"
|
|
1223
1282
|
] : [],
|
|
1224
1283
|
" },",
|
|
@@ -1245,7 +1304,7 @@ function sharedTypesApiTypesContent() {
|
|
|
1245
1304
|
""
|
|
1246
1305
|
].join("\n");
|
|
1247
1306
|
}
|
|
1248
|
-
function sharedTypesPackageJsonContent(packageName) {
|
|
1307
|
+
function sharedTypesPackageJsonContent(packageName, sharedTag = "scope:shared") {
|
|
1249
1308
|
return `${JSON.stringify({
|
|
1250
1309
|
name: packageName,
|
|
1251
1310
|
version: "0.0.1",
|
|
@@ -1262,13 +1321,296 @@ function sharedTypesPackageJsonContent(packageName) {
|
|
|
1262
1321
|
},
|
|
1263
1322
|
nx: {
|
|
1264
1323
|
projectType: "library",
|
|
1265
|
-
tags: [
|
|
1324
|
+
tags: [sharedTag],
|
|
1266
1325
|
targets: {}
|
|
1267
1326
|
}
|
|
1268
1327
|
}, null, 2)}
|
|
1269
1328
|
`;
|
|
1270
1329
|
}
|
|
1271
1330
|
|
|
1331
|
+
// ../template-engine/dist/backend/compose-backend-wiring.js
|
|
1332
|
+
var DEFAULT_SHARED_TYPES_ROOT = "libs/shared-types";
|
|
1333
|
+
function readSharedTypesConfig(config) {
|
|
1334
|
+
const value = config?.["sharedTypes"];
|
|
1335
|
+
if (value === false)
|
|
1336
|
+
return false;
|
|
1337
|
+
if (typeof value !== "object" || value === null)
|
|
1338
|
+
return { path: DEFAULT_SHARED_TYPES_ROOT };
|
|
1339
|
+
const record = value;
|
|
1340
|
+
const path = typeof record["path"] === "string" && record["path"].trim() !== "" ? record["path"].replace(/\/+$/, "") : DEFAULT_SHARED_TYPES_ROOT;
|
|
1341
|
+
const importPath = typeof record["importPath"] === "string" && record["importPath"].trim() !== "" ? record["importPath"] : void 0;
|
|
1342
|
+
return importPath === void 0 ? { path } : { path, importPath };
|
|
1343
|
+
}
|
|
1344
|
+
function composeBackendWiring(input) {
|
|
1345
|
+
const scoped = /^@([^/]+)\//.exec(input.workspaceName);
|
|
1346
|
+
const scope = scoped ? `@${scoped[1]}` : `@${input.workspaceName.split("/").pop() || "workspace"}`;
|
|
1347
|
+
const sharedTypesRoot = input.sharedTypes?.path ?? DEFAULT_SHARED_TYPES_ROOT;
|
|
1348
|
+
const sharedTypesPackageName = input.sharedTypes?.importPath ?? `${scope}/shared-types`;
|
|
1349
|
+
const tagByRole = input.tags ?? DEFAULT_BOUNDARY_TAGS;
|
|
1350
|
+
return {
|
|
1351
|
+
sharedTypesFiles: [
|
|
1352
|
+
{ filePath: `${sharedTypesRoot}/package.json`, content: sharedTypesPackageJsonContent(sharedTypesPackageName, tagByRole.shared) },
|
|
1353
|
+
{ filePath: `${sharedTypesRoot}/src/index.ts`, content: sharedTypesIndexContent() },
|
|
1354
|
+
{ filePath: `${sharedTypesRoot}/src/lib/api-types.ts`, content: sharedTypesApiTypesContent() }
|
|
1355
|
+
],
|
|
1356
|
+
sharedTypesPackageName,
|
|
1357
|
+
sharedTypesRoot,
|
|
1358
|
+
tagByRole
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
function addSharedTypesNpmWorkspace(packageJsonText, entry = DEFAULT_SHARED_TYPES_ROOT) {
|
|
1362
|
+
let parsed;
|
|
1363
|
+
try {
|
|
1364
|
+
parsed = JSON.parse(packageJsonText);
|
|
1365
|
+
} catch {
|
|
1366
|
+
return { status: "unrecognized" };
|
|
1367
|
+
}
|
|
1368
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
1369
|
+
return { status: "unrecognized" };
|
|
1370
|
+
const pkg = parsed;
|
|
1371
|
+
const field = pkg["workspaces"];
|
|
1372
|
+
let target;
|
|
1373
|
+
if (field === void 0) {
|
|
1374
|
+
target = [];
|
|
1375
|
+
} else if (Array.isArray(field)) {
|
|
1376
|
+
target = field;
|
|
1377
|
+
} else if (field !== null && typeof field === "object" && Array.isArray(field.packages)) {
|
|
1378
|
+
target = field.packages;
|
|
1379
|
+
} else {
|
|
1380
|
+
return { status: "unrecognized" };
|
|
1381
|
+
}
|
|
1382
|
+
const parentGlob = `${entry.split("/").slice(0, -1).join("/")}/*`;
|
|
1383
|
+
if (target.some((existing) => existing === entry || existing === parentGlob))
|
|
1384
|
+
return { status: "present" };
|
|
1385
|
+
const surgical = insertSharedTypesWorkspace(packageJsonText, field === void 0 ? "absent" : Array.isArray(field) ? "array" : "object", JSON.stringify(entry));
|
|
1386
|
+
if (surgical !== null)
|
|
1387
|
+
return { status: "added", text: surgical };
|
|
1388
|
+
if (field === void 0)
|
|
1389
|
+
pkg["workspaces"] = target;
|
|
1390
|
+
target.push(entry);
|
|
1391
|
+
const indent = /\n([ \t]+)"/.exec(packageJsonText)?.[1] ?? " ";
|
|
1392
|
+
const trailing = packageJsonText.endsWith("\n") ? "\n" : "";
|
|
1393
|
+
return { status: "added", text: `${JSON.stringify(pkg, null, indent)}${trailing}` };
|
|
1394
|
+
}
|
|
1395
|
+
function skipJsonValue(text, start) {
|
|
1396
|
+
if (text[start] === '"') {
|
|
1397
|
+
let i = start + 1;
|
|
1398
|
+
while (i < text.length && text[i] !== '"')
|
|
1399
|
+
i += text[i] === "\\" ? 2 : 1;
|
|
1400
|
+
return i + 1;
|
|
1401
|
+
}
|
|
1402
|
+
if (text[start] === "[" || text[start] === "{") {
|
|
1403
|
+
let depth = 0;
|
|
1404
|
+
let i = start;
|
|
1405
|
+
while (i < text.length) {
|
|
1406
|
+
const c = text[i];
|
|
1407
|
+
if (c === '"') {
|
|
1408
|
+
i = skipJsonValue(text, i);
|
|
1409
|
+
continue;
|
|
1410
|
+
}
|
|
1411
|
+
if (c === "[" || c === "{")
|
|
1412
|
+
depth++;
|
|
1413
|
+
if (c === "]" || c === "}") {
|
|
1414
|
+
depth--;
|
|
1415
|
+
if (depth === 0)
|
|
1416
|
+
return i + 1;
|
|
1417
|
+
}
|
|
1418
|
+
i++;
|
|
1419
|
+
}
|
|
1420
|
+
return -1;
|
|
1421
|
+
}
|
|
1422
|
+
const literal = /^[^,}\]\s]+/.exec(text.slice(start));
|
|
1423
|
+
return literal ? start + literal[0].length : -1;
|
|
1424
|
+
}
|
|
1425
|
+
function objectMembers(text, open) {
|
|
1426
|
+
const members = [];
|
|
1427
|
+
let i = open + 1;
|
|
1428
|
+
for (; ; ) {
|
|
1429
|
+
while (/[\s,]/.test(text[i] ?? ""))
|
|
1430
|
+
i++;
|
|
1431
|
+
if (text[i] === "}")
|
|
1432
|
+
return members;
|
|
1433
|
+
if (text[i] !== '"')
|
|
1434
|
+
return null;
|
|
1435
|
+
const keyEnd = skipJsonValue(text, i);
|
|
1436
|
+
const key = JSON.parse(text.slice(i, keyEnd));
|
|
1437
|
+
i = keyEnd;
|
|
1438
|
+
while (/\s/.test(text[i] ?? ""))
|
|
1439
|
+
i++;
|
|
1440
|
+
if (text[i] !== ":")
|
|
1441
|
+
return null;
|
|
1442
|
+
i++;
|
|
1443
|
+
while (/\s/.test(text[i] ?? ""))
|
|
1444
|
+
i++;
|
|
1445
|
+
const valueEnd = skipJsonValue(text, i);
|
|
1446
|
+
if (valueEnd === -1)
|
|
1447
|
+
return null;
|
|
1448
|
+
members.push({ key, valueStart: i, valueEnd });
|
|
1449
|
+
i = valueEnd;
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
function appendToArray(text, open, close, eol, quoted) {
|
|
1453
|
+
const inner = text.slice(open + 1, close - 1);
|
|
1454
|
+
if (inner.trim() === "")
|
|
1455
|
+
return `${text.slice(0, open + 1)}${quoted}${text.slice(close - 1)}`;
|
|
1456
|
+
if (!inner.includes("\n")) {
|
|
1457
|
+
const body = inner.replace(/\s+$/, "");
|
|
1458
|
+
return `${text.slice(0, open + 1)}${body}, ${quoted}${inner.slice(body.length)}${text.slice(close - 1)}`;
|
|
1459
|
+
}
|
|
1460
|
+
const lastEntryLineStart = text.lastIndexOf("\n", open + 1 + inner.trimEnd().length - 1) + 1;
|
|
1461
|
+
const itemIndent = /^[ \t]*/.exec(text.slice(lastEntryLineStart))?.[0] ?? " ";
|
|
1462
|
+
const bodyEnd = open + 1 + inner.trimEnd().length;
|
|
1463
|
+
return `${text.slice(0, bodyEnd)},${eol}${itemIndent}${quoted}${text.slice(bodyEnd)}`;
|
|
1464
|
+
}
|
|
1465
|
+
function insertSharedTypesWorkspace(text, form, quoted) {
|
|
1466
|
+
const eol = text.includes("\r\n") ? "\r\n" : "\n";
|
|
1467
|
+
const rootOpen = text.indexOf("{");
|
|
1468
|
+
if (rootOpen === -1)
|
|
1469
|
+
return null;
|
|
1470
|
+
const members = objectMembers(text, rootOpen);
|
|
1471
|
+
if (members === null)
|
|
1472
|
+
return null;
|
|
1473
|
+
const workspaces = members.find((member) => member.key === "workspaces");
|
|
1474
|
+
if (form === "absent") {
|
|
1475
|
+
const last = members[members.length - 1];
|
|
1476
|
+
const indent = /\n([ \t]+)"/.exec(text)?.[1] ?? " ";
|
|
1477
|
+
if (last === void 0) {
|
|
1478
|
+
const close = skipJsonValue(text, rootOpen) - 1;
|
|
1479
|
+
return `${text.slice(0, rootOpen + 1)}${eol}${indent}"workspaces": [${quoted}]${eol}${text.slice(close)}`;
|
|
1480
|
+
}
|
|
1481
|
+
return `${text.slice(0, last.valueEnd)},${eol}${indent}"workspaces": [${quoted}]${text.slice(last.valueEnd)}`;
|
|
1482
|
+
}
|
|
1483
|
+
if (workspaces === void 0)
|
|
1484
|
+
return null;
|
|
1485
|
+
if (form === "array") {
|
|
1486
|
+
if (text[workspaces.valueStart] !== "[")
|
|
1487
|
+
return null;
|
|
1488
|
+
return appendToArray(text, workspaces.valueStart, workspaces.valueEnd, eol, quoted);
|
|
1489
|
+
}
|
|
1490
|
+
if (text[workspaces.valueStart] !== "{")
|
|
1491
|
+
return null;
|
|
1492
|
+
const packages = objectMembers(text, workspaces.valueStart)?.find((member) => member.key === "packages");
|
|
1493
|
+
if (packages === void 0 || text[packages.valueStart] !== "[")
|
|
1494
|
+
return null;
|
|
1495
|
+
return appendToArray(text, packages.valueStart, packages.valueEnd, eol, quoted);
|
|
1496
|
+
}
|
|
1497
|
+
function addSharedTypesWorkspaceGlob(yamlText, entry = DEFAULT_SHARED_TYPES_ROOT) {
|
|
1498
|
+
const escape = (value) => value.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
|
|
1499
|
+
const parentGlob = `${entry.split("/").slice(0, -1).join("/")}/*`;
|
|
1500
|
+
if (new RegExp(`^\\s*-\\s*['"]?(${escape(entry)}|${escape(parentGlob)})['"]?\\s*$`, "m").test(yamlText)) {
|
|
1501
|
+
return { status: "present" };
|
|
1502
|
+
}
|
|
1503
|
+
const lines = yamlText.split("\n");
|
|
1504
|
+
const packagesIndex = lines.findIndex((line) => /^packages:\s*$/.test(line));
|
|
1505
|
+
if (packagesIndex === -1)
|
|
1506
|
+
return { status: "unrecognized" };
|
|
1507
|
+
let lastEntry = packagesIndex;
|
|
1508
|
+
for (let i = packagesIndex + 1; i < lines.length; i++) {
|
|
1509
|
+
if (/^\s+-\s+\S/.test(lines[i])) {
|
|
1510
|
+
lastEntry = i;
|
|
1511
|
+
} else if (lines[i].trim() === "") {
|
|
1512
|
+
continue;
|
|
1513
|
+
} else {
|
|
1514
|
+
break;
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
if (lastEntry === packagesIndex)
|
|
1518
|
+
return { status: "unrecognized" };
|
|
1519
|
+
const indent = /^(\s+)-/.exec(lines[lastEntry])?.[1] ?? " ";
|
|
1520
|
+
lines.splice(lastEntry + 1, 0, `${indent}- '${entry}'`);
|
|
1521
|
+
return { status: "added", text: lines.join("\n") };
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
// ../template-engine/dist/frameworks/profiles.js
|
|
1525
|
+
var ALL_LAYERS = ["app", "pages", "features", "entities", "shared"];
|
|
1526
|
+
var NEXT = {
|
|
1527
|
+
id: "next",
|
|
1528
|
+
recognised: true,
|
|
1529
|
+
// Next.js's own app/ (App Router) directory is the composition root; FSD's Next.js guide
|
|
1530
|
+
// names the FSD app and pages layers _app and _pages, since Next.js reads src/app and
|
|
1531
|
+
// src/pages as its routers.
|
|
1532
|
+
layers: ["pages", "features", "entities", "shared"],
|
|
1533
|
+
layerDirs: { app: "_app", pages: "_pages" },
|
|
1534
|
+
buildOutputDirs: [".next", "out"],
|
|
1535
|
+
platformSuffixes: [],
|
|
1536
|
+
routerDirs: ["pages"]
|
|
1537
|
+
};
|
|
1538
|
+
var REACT_VITE = {
|
|
1539
|
+
id: "react-vite",
|
|
1540
|
+
recognised: true,
|
|
1541
|
+
layers: ALL_LAYERS,
|
|
1542
|
+
layerDirs: {},
|
|
1543
|
+
buildOutputDirs: ["dist"],
|
|
1544
|
+
platformSuffixes: [],
|
|
1545
|
+
routerDirs: []
|
|
1546
|
+
};
|
|
1547
|
+
var EXPO_ROUTER = {
|
|
1548
|
+
id: "expo",
|
|
1549
|
+
recognised: true,
|
|
1550
|
+
layers: ALL_LAYERS,
|
|
1551
|
+
layerDirs: { app: "_app" },
|
|
1552
|
+
buildOutputDirs: [".expo", "dist"],
|
|
1553
|
+
platformSuffixes: ["ios", "android", "native", "web"],
|
|
1554
|
+
routerDirs: []
|
|
1555
|
+
};
|
|
1556
|
+
var EXPO = { ...EXPO_ROUTER, layerDirs: {} };
|
|
1557
|
+
var GENERIC = {
|
|
1558
|
+
id: "generic",
|
|
1559
|
+
recognised: false,
|
|
1560
|
+
layers: ALL_LAYERS,
|
|
1561
|
+
layerDirs: {},
|
|
1562
|
+
buildOutputDirs: ["dist", "build"],
|
|
1563
|
+
platformSuffixes: [],
|
|
1564
|
+
routerDirs: []
|
|
1565
|
+
};
|
|
1566
|
+
var PROFILES = { next: NEXT, "react-vite": REACT_VITE, expo: EXPO_ROUTER, generic: GENERIC };
|
|
1567
|
+
function profileById(id) {
|
|
1568
|
+
return PROFILES[id];
|
|
1569
|
+
}
|
|
1570
|
+
function allFrameworkProfiles() {
|
|
1571
|
+
return Object.values(PROFILES);
|
|
1572
|
+
}
|
|
1573
|
+
var NEXT_CONFIGS = ["next.config.js", "next.config.mjs", "next.config.ts", "next.config.cjs"];
|
|
1574
|
+
var VITE_CONFIGS = ["vite.config.ts", "vite.config.mts", "vite.config.js", "vite.config.mjs"];
|
|
1575
|
+
var EXPO_CONFIGS = ["app.config.ts", "app.config.js", "app.config.mjs"];
|
|
1576
|
+
function hasExpoKey(input, file) {
|
|
1577
|
+
const text = input.read(file);
|
|
1578
|
+
if (typeof text !== "string")
|
|
1579
|
+
return false;
|
|
1580
|
+
try {
|
|
1581
|
+
return typeof JSON.parse(text)["expo"] === "object";
|
|
1582
|
+
} catch {
|
|
1583
|
+
return false;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
function dependsOn(input, manifest, name) {
|
|
1587
|
+
const text = input.read(manifest);
|
|
1588
|
+
if (typeof text !== "string")
|
|
1589
|
+
return false;
|
|
1590
|
+
try {
|
|
1591
|
+
const parsed = JSON.parse(text);
|
|
1592
|
+
return ["dependencies", "devDependencies", "peerDependencies"].some((field) => parsed[field]?.[name] !== void 0);
|
|
1593
|
+
} catch {
|
|
1594
|
+
return false;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
function detectFramework(input) {
|
|
1598
|
+
const prefix = input.projectRoot === "" || input.projectRoot === "." ? "" : `${input.projectRoot}/`;
|
|
1599
|
+
const executors = input.executors ?? [];
|
|
1600
|
+
if (NEXT_CONFIGS.some((file) => input.exists(`${prefix}${file}`)) || executors.some((executor) => executor.startsWith("@nx/next:"))) {
|
|
1601
|
+
return NEXT;
|
|
1602
|
+
}
|
|
1603
|
+
const dependsAnywhere = (name) => dependsOn(input, `${prefix}package.json`, name) || dependsOn(input, "package.json", name);
|
|
1604
|
+
const usesExpo = executors.some((executor) => executor.startsWith("@nx/expo:")) || hasExpoKey(input, `${prefix}app.json`) || EXPO_CONFIGS.some((file) => input.exists(`${prefix}${file}`)) || dependsOn(input, `${prefix}package.json`, "expo");
|
|
1605
|
+
if (usesExpo)
|
|
1606
|
+
return dependsAnywhere("expo-router") ? EXPO_ROUTER : EXPO;
|
|
1607
|
+
const usesReact = dependsAnywhere("react");
|
|
1608
|
+
const usesVite = VITE_CONFIGS.some((file) => input.exists(`${prefix}${file}`)) || executors.some((executor) => executor.startsWith("@nx/vite:"));
|
|
1609
|
+
if (usesVite && usesReact)
|
|
1610
|
+
return REACT_VITE;
|
|
1611
|
+
return GENERIC;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1272
1614
|
// ../template-engine/dist/fsd/fsd-eslint-config.js
|
|
1273
1615
|
function classifyFsdEslintConfig(configText) {
|
|
1274
1616
|
return maskComments(configText).includes(FSD_ESLINT_MODULE) ? "applied" : "absent";
|
|
@@ -1276,21 +1618,47 @@ function classifyFsdEslintConfig(configText) {
|
|
|
1276
1618
|
var EXPORT_DEFAULT_ARRAY = /export\s+default\s+\[/g;
|
|
1277
1619
|
var IMPORT_STATEMENT = /^import\b[^;]*?\bfrom\s+['"][^'"]+['"]\s*;?[ \t]*$/gm;
|
|
1278
1620
|
var CLOSING_BRACKET = /\]\s*;?\s*$/;
|
|
1279
|
-
function
|
|
1621
|
+
function closingBracketOf(masked, open) {
|
|
1622
|
+
let depth = 0;
|
|
1623
|
+
for (let i = open; i < masked.length; i++) {
|
|
1624
|
+
const ch = masked[i];
|
|
1625
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
1626
|
+
for (i++; i < masked.length && masked[i] !== ch; i++)
|
|
1627
|
+
if (masked[i] === "\\")
|
|
1628
|
+
i++;
|
|
1629
|
+
continue;
|
|
1630
|
+
}
|
|
1631
|
+
if (ch === "[")
|
|
1632
|
+
depth++;
|
|
1633
|
+
else if (ch === "]" && --depth === 0)
|
|
1634
|
+
return i;
|
|
1635
|
+
}
|
|
1636
|
+
return -1;
|
|
1637
|
+
}
|
|
1638
|
+
function namedConfigArrayEnd(masked) {
|
|
1639
|
+
const exported = [...masked.matchAll(/export\s+default\s+([A-Za-z_$][\w$]*)\s*;?/g)];
|
|
1640
|
+
if (exported.length !== 1)
|
|
1641
|
+
return -1;
|
|
1642
|
+
const declaration = new RegExp(`(?:const|let|var)\\s+${exported[0][1].replace(/\$/g, "\\$")}\\s*=\\s*(?:defineConfig\\s*\\(\\s*)?\\[`).exec(masked);
|
|
1643
|
+
if (declaration === null)
|
|
1644
|
+
return -1;
|
|
1645
|
+
return closingBracketOf(masked, declaration.index + declaration[0].length - 1);
|
|
1646
|
+
}
|
|
1647
|
+
function applyFsdEslintConfig(configText, severity, root = "src", framework = {}) {
|
|
1280
1648
|
if (classifyFsdEslintConfig(configText) === "applied")
|
|
1281
1649
|
return { status: "already-applied" };
|
|
1282
1650
|
const masked = maskComments(configText);
|
|
1283
1651
|
EXPORT_DEFAULT_ARRAY.lastIndex = 0;
|
|
1284
1652
|
const exportMatches = masked.match(EXPORT_DEFAULT_ARRAY) ?? [];
|
|
1285
1653
|
const closing = CLOSING_BRACKET.exec(masked);
|
|
1286
|
-
|
|
1654
|
+
const closingIndex = exportMatches.length === 1 && closing !== null ? closing.index : namedConfigArrayEnd(masked);
|
|
1655
|
+
if (closingIndex === -1)
|
|
1287
1656
|
return { status: "unrecognized" };
|
|
1288
1657
|
const eol = configText.includes("\r\n") ? "\r\n" : "\n";
|
|
1289
|
-
const closingIndex = closing.index;
|
|
1290
1658
|
const before = configText.slice(0, closingIndex);
|
|
1291
1659
|
const lastNonSpace = before.replace(/\s+$/, "");
|
|
1292
1660
|
const needsComma = !lastNonSpace.endsWith(",") && !lastNonSpace.endsWith("[");
|
|
1293
|
-
const entryText = `${needsComma ? "," : ""}${eol} ${buildFsdEslintEntry(severity)}${eol}`;
|
|
1661
|
+
const entryText = `${needsComma ? "," : ""}${eol} ${buildFsdEslintEntry(severity, root, framework)}${eol}`;
|
|
1294
1662
|
const withEntry = `${lastNonSpace}${entryText}${configText.slice(closingIndex)}`;
|
|
1295
1663
|
IMPORT_STATEMENT.lastIndex = 0;
|
|
1296
1664
|
let lastImportEnd = -1;
|
|
@@ -1302,20 +1670,12 @@ function applyFsdEslintConfig(configText, severity) {
|
|
|
1302
1670
|
const text = lastImportEnd === -1 ? `${importLine}${eol}${eol}${withEntry}` : `${withEntry.slice(0, lastImportEnd)}${eol}${importLine}${withEntry.slice(lastImportEnd)}`;
|
|
1303
1671
|
return { status: "applied", text };
|
|
1304
1672
|
}
|
|
1305
|
-
function buildFsdEslintPasteSnippet(severity, locale) {
|
|
1306
|
-
return buildFsdEslintSnippet({ severity, locale });
|
|
1307
|
-
}
|
|
1308
|
-
var REACT_VITE_LAYERS = ["app", "pages", "features", "entities", "shared"];
|
|
1309
|
-
var NEXT_LAYERS = ["pages", "features", "entities", "shared"];
|
|
1310
|
-
function fsdLayerDirName(frontend, layer) {
|
|
1311
|
-
return frontend === "next" && (layer === "pages" || layer === "app") ? `_${layer}` : layer;
|
|
1312
|
-
}
|
|
1313
|
-
function fsdLayersFor(frontend) {
|
|
1314
|
-
return frontend === "next" ? [...NEXT_LAYERS] : [...REACT_VITE_LAYERS];
|
|
1673
|
+
function buildFsdEslintPasteSnippet(severity, locale, root = "src", framework = {}) {
|
|
1674
|
+
return buildFsdEslintSnippet({ severity, locale, root, framework });
|
|
1315
1675
|
}
|
|
1316
|
-
function composeFsdLayerGuide({
|
|
1317
|
-
const layers =
|
|
1318
|
-
return buildFsdLayerGuide({ layers, directories: layers.map((layer) =>
|
|
1676
|
+
function composeFsdLayerGuide({ profile, sourceRoot, locale }) {
|
|
1677
|
+
const layers = [...profile.layers];
|
|
1678
|
+
return buildFsdLayerGuide({ layers, directories: layers.map((layer) => profile.layerDirs[layer] ?? layer), sourceRoot, locale });
|
|
1319
1679
|
}
|
|
1320
1680
|
|
|
1321
1681
|
// ../template-engine/dist/ai-hosts/ai-hosts.js
|
|
@@ -1346,18 +1706,13 @@ function reviewProviderFor(hosts) {
|
|
|
1346
1706
|
}
|
|
1347
1707
|
|
|
1348
1708
|
// ../template-engine/dist/presets/preset-config.js
|
|
1349
|
-
var REACT_VITE_LAYERS2 = ["app", "pages", "features", "entities", "shared"];
|
|
1350
|
-
var NEXT_LAYERS2 = ["pages", "features", "entities", "shared"];
|
|
1351
1709
|
function buildFsdConfig(enforcement, frontend) {
|
|
1352
1710
|
return {
|
|
1353
1711
|
// All three presets enable FSD — it is on whenever there is a
|
|
1354
1712
|
// frontend; a backend-only workspace has none to structure.
|
|
1355
1713
|
enabled: frontend !== "none",
|
|
1356
1714
|
enforcement,
|
|
1357
|
-
layerMapping:
|
|
1358
|
-
"react-vite": REACT_VITE_LAYERS2,
|
|
1359
|
-
next: NEXT_LAYERS2
|
|
1360
|
-
}
|
|
1715
|
+
layerMapping: Object.fromEntries(allFrameworkProfiles().map((profile) => [profile.id, [...profile.layers]]))
|
|
1361
1716
|
};
|
|
1362
1717
|
}
|
|
1363
1718
|
var PRESET_DEFAULTS = {
|
|
@@ -1459,7 +1814,13 @@ ${section.body}`).join("\n\n")}
|
|
|
1459
1814
|
}
|
|
1460
1815
|
function composeAgentRules(input) {
|
|
1461
1816
|
const sectionIds = input.sections ?? resolveDefaultAgentSectionIds(input.teamRules ?? false, input.fsdStructure ?? false);
|
|
1462
|
-
const
|
|
1817
|
+
const notes = input.fsdPolicy === void 0 ? "" : buildFsdPolicyNotes(input.locale, input.fsdPolicy);
|
|
1818
|
+
const sections = sectionIds.map((id) => {
|
|
1819
|
+
const section = AGENT_RULE_SECTIONS[input.locale][id];
|
|
1820
|
+
return id === FSD_AGENT_SECTION_ID && notes !== "" ? { ...section, body: `${section.body}
|
|
1821
|
+
|
|
1822
|
+
${notes}` } : section;
|
|
1823
|
+
});
|
|
1463
1824
|
const content = renderSections(sections);
|
|
1464
1825
|
return input.files.map((filePath) => ({ filePath, content }));
|
|
1465
1826
|
}
|
|
@@ -1475,297 +1836,1102 @@ function validateAgentRules(content, extraRequired = []) {
|
|
|
1475
1836
|
}
|
|
1476
1837
|
|
|
1477
1838
|
// ../template-engine/dist/agent-rules/marker-block.js
|
|
1839
|
+
import { createHash } from "node:crypto";
|
|
1478
1840
|
var AGENT_RULES_BLOCK_START = "<!-- nx-archweave:agent-rules start -->";
|
|
1479
1841
|
var AGENT_RULES_BLOCK_END = "<!-- nx-archweave:agent-rules end -->";
|
|
1842
|
+
var START_MARKER = /<!-- nx-archweave:agent-rules start(?: hash=([0-9a-f]{12}))? -->/g;
|
|
1843
|
+
function normalizeForHash(text) {
|
|
1844
|
+
return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "");
|
|
1845
|
+
}
|
|
1846
|
+
function hashOf(body) {
|
|
1847
|
+
return createHash("sha256").update(normalizeForHash(body)).digest("hex").slice(0, 12);
|
|
1848
|
+
}
|
|
1849
|
+
function withFinalNewline(body) {
|
|
1850
|
+
return body.endsWith("\n") ? body : `${body}
|
|
1851
|
+
`;
|
|
1852
|
+
}
|
|
1480
1853
|
function renderAgentRulesBlock(body, eol = "\n") {
|
|
1481
|
-
const
|
|
1482
|
-
|
|
1483
|
-
|
|
1854
|
+
const content = withFinalNewline(normalizeForHash(body));
|
|
1855
|
+
const lines = `<!-- nx-archweave:agent-rules start hash=${hashOf(content)} -->
|
|
1856
|
+
${content}${AGENT_RULES_BLOCK_END}`;
|
|
1484
1857
|
return eol === "\n" ? lines : lines.replace(/\r?\n/g, eol);
|
|
1485
1858
|
}
|
|
1486
1859
|
function countOf(text, needle) {
|
|
1487
1860
|
return text.split(needle).length - 1;
|
|
1488
1861
|
}
|
|
1489
|
-
function applyAgentRulesBlock(existing, body) {
|
|
1862
|
+
function applyAgentRulesBlock(existing, body, options = {}) {
|
|
1490
1863
|
if (existing === null || existing.length === 0)
|
|
1491
1864
|
return { kind: "create", text: `${renderAgentRulesBlock(body)}
|
|
1492
1865
|
` };
|
|
1493
1866
|
const eol = existing.includes("\r\n") ? "\r\n" : "\n";
|
|
1494
1867
|
const block = renderAgentRulesBlock(body, eol);
|
|
1495
|
-
const starts =
|
|
1868
|
+
const starts = [...existing.matchAll(START_MARKER)];
|
|
1496
1869
|
const ends = countOf(existing, AGENT_RULES_BLOCK_END);
|
|
1497
|
-
if (starts === 0 && ends === 0) {
|
|
1870
|
+
if (starts.length === 0 && ends === 0) {
|
|
1498
1871
|
const head = existing.endsWith("\n") ? existing : `${existing}${eol}`;
|
|
1499
1872
|
return { kind: "append", text: `${head}${eol}${block}${eol}` };
|
|
1500
1873
|
}
|
|
1501
|
-
if (starts !== 1 || ends !== 1) {
|
|
1874
|
+
if (starts.length !== 1 || ends !== 1) {
|
|
1502
1875
|
return {
|
|
1503
1876
|
kind: "unrecognized",
|
|
1504
|
-
reason: `expected one ${AGENT_RULES_BLOCK_START} \u2026 ${AGENT_RULES_BLOCK_END} pair, found ${starts} start and ${ends} end marker(s)`
|
|
1877
|
+
reason: `expected one ${AGENT_RULES_BLOCK_START} \u2026 ${AGENT_RULES_BLOCK_END} pair, found ${starts.length} start and ${ends} end marker(s)`
|
|
1505
1878
|
};
|
|
1506
1879
|
}
|
|
1507
|
-
const
|
|
1880
|
+
const startMatch = starts[0];
|
|
1881
|
+
const start = startMatch.index ?? 0;
|
|
1508
1882
|
const end = existing.indexOf(AGENT_RULES_BLOCK_END);
|
|
1509
1883
|
if (end < start)
|
|
1510
1884
|
return { kind: "unrecognized", reason: "the end marker comes before the start marker" };
|
|
1511
1885
|
const text = existing.slice(0, start) + block + existing.slice(end + AGENT_RULES_BLOCK_END.length);
|
|
1512
|
-
|
|
1886
|
+
if (text === existing)
|
|
1887
|
+
return { kind: "unchanged" };
|
|
1888
|
+
const bodyStart = start + startMatch[0].length + (existing.startsWith("\r\n", start + startMatch[0].length) ? 2 : 1);
|
|
1889
|
+
const existingBody = normalizeForHash(existing.slice(bodyStart, end));
|
|
1890
|
+
const generatedBody = withFinalNewline(normalizeForHash(body));
|
|
1891
|
+
const recordedHash = startMatch[1];
|
|
1892
|
+
const untouched = recordedHash !== void 0 ? hashOf(existingBody) === recordedHash : existingBody === generatedBody;
|
|
1893
|
+
if (!untouched && options.overwrite !== true)
|
|
1894
|
+
return { kind: "user-modified", existingBody, generatedBody };
|
|
1895
|
+
return { kind: "replace", text };
|
|
1513
1896
|
}
|
|
1514
1897
|
|
|
1515
|
-
//
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
return
|
|
1521
|
-
sharedTypesFiles: [
|
|
1522
|
-
{ filePath: "libs/shared-types/package.json", content: sharedTypesPackageJsonContent(sharedTypesPackageName) },
|
|
1523
|
-
{ filePath: "libs/shared-types/src/index.ts", content: sharedTypesIndexContent() },
|
|
1524
|
-
{ filePath: "libs/shared-types/src/lib/api-types.ts", content: sharedTypesApiTypesContent() }
|
|
1525
|
-
],
|
|
1526
|
-
sharedTypesPackageName,
|
|
1527
|
-
tagByRole: { frontend: "scope:frontend", backend: "scope:backend", shared: "scope:shared" }
|
|
1528
|
-
};
|
|
1898
|
+
// src/generators/add-fsd-lite/generator.ts
|
|
1899
|
+
import { posix } from "node:path";
|
|
1900
|
+
|
|
1901
|
+
// src/generators/shared/dry-run.ts
|
|
1902
|
+
function resolveDryRun(options) {
|
|
1903
|
+
return process.env["NX_DRY_RUN"] === "true" || (options.dryRun ?? false);
|
|
1529
1904
|
}
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1905
|
+
|
|
1906
|
+
// src/generators/shared/frontend-app.ts
|
|
1907
|
+
import { getProjects } from "@nx/devkit";
|
|
1908
|
+
|
|
1909
|
+
// ../../node_modules/.pnpm/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/scanner.js
|
|
1910
|
+
function createScanner(text, ignoreTrivia = false) {
|
|
1911
|
+
const len = text.length;
|
|
1912
|
+
let pos = 0, value = "", tokenOffset = 0, token = 16, lineNumber = 0, lineStartOffset = 0, tokenLineStartOffset = 0, prevTokenLineStartOffset = 0, scanError = 0;
|
|
1913
|
+
function scanHexDigits(count, exact) {
|
|
1914
|
+
let digits = 0;
|
|
1915
|
+
let value2 = 0;
|
|
1916
|
+
while (digits < count || !exact) {
|
|
1917
|
+
let ch = text.charCodeAt(pos);
|
|
1918
|
+
if (ch >= 48 && ch <= 57) {
|
|
1919
|
+
value2 = value2 * 16 + ch - 48;
|
|
1920
|
+
} else if (ch >= 65 && ch <= 70) {
|
|
1921
|
+
value2 = value2 * 16 + ch - 65 + 10;
|
|
1922
|
+
} else if (ch >= 97 && ch <= 102) {
|
|
1923
|
+
value2 = value2 * 16 + ch - 97 + 10;
|
|
1924
|
+
} else {
|
|
1925
|
+
break;
|
|
1926
|
+
}
|
|
1927
|
+
pos++;
|
|
1928
|
+
digits++;
|
|
1929
|
+
}
|
|
1930
|
+
if (digits < count) {
|
|
1931
|
+
value2 = -1;
|
|
1932
|
+
}
|
|
1933
|
+
return value2;
|
|
1550
1934
|
}
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
pkg["workspaces"] = target;
|
|
1558
|
-
target.push("libs/shared-types");
|
|
1559
|
-
const indent = /\n([ \t]+)"/.exec(packageJsonText)?.[1] ?? " ";
|
|
1560
|
-
const trailing = packageJsonText.endsWith("\n") ? "\n" : "";
|
|
1561
|
-
return { status: "added", text: `${JSON.stringify(pkg, null, indent)}${trailing}` };
|
|
1562
|
-
}
|
|
1563
|
-
var SHARED_TYPES_ENTRY = '"libs/shared-types"';
|
|
1564
|
-
function skipJsonValue(text, start) {
|
|
1565
|
-
if (text[start] === '"') {
|
|
1566
|
-
let i = start + 1;
|
|
1567
|
-
while (i < text.length && text[i] !== '"')
|
|
1568
|
-
i += text[i] === "\\" ? 2 : 1;
|
|
1569
|
-
return i + 1;
|
|
1935
|
+
function setPosition(newPosition) {
|
|
1936
|
+
pos = newPosition;
|
|
1937
|
+
value = "";
|
|
1938
|
+
tokenOffset = 0;
|
|
1939
|
+
token = 16;
|
|
1940
|
+
scanError = 0;
|
|
1570
1941
|
}
|
|
1571
|
-
|
|
1572
|
-
let
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1942
|
+
function scanNumber() {
|
|
1943
|
+
let start = pos;
|
|
1944
|
+
if (text.charCodeAt(pos) === 48) {
|
|
1945
|
+
pos++;
|
|
1946
|
+
} else {
|
|
1947
|
+
pos++;
|
|
1948
|
+
while (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
1949
|
+
pos++;
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
if (pos < text.length && text.charCodeAt(pos) === 46) {
|
|
1953
|
+
pos++;
|
|
1954
|
+
if (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
1955
|
+
pos++;
|
|
1956
|
+
while (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
1957
|
+
pos++;
|
|
1958
|
+
}
|
|
1959
|
+
} else {
|
|
1960
|
+
scanError = 3;
|
|
1961
|
+
return text.substring(start, pos);
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
let end = pos;
|
|
1965
|
+
if (pos < text.length && (text.charCodeAt(pos) === 69 || text.charCodeAt(pos) === 101)) {
|
|
1966
|
+
pos++;
|
|
1967
|
+
if (pos < text.length && text.charCodeAt(pos) === 43 || text.charCodeAt(pos) === 45) {
|
|
1968
|
+
pos++;
|
|
1969
|
+
}
|
|
1970
|
+
if (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
1971
|
+
pos++;
|
|
1972
|
+
while (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
1973
|
+
pos++;
|
|
1974
|
+
}
|
|
1975
|
+
end = pos;
|
|
1976
|
+
} else {
|
|
1977
|
+
scanError = 3;
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
return text.substring(start, end);
|
|
1981
|
+
}
|
|
1982
|
+
function scanString() {
|
|
1983
|
+
let result = "", start = pos;
|
|
1984
|
+
while (true) {
|
|
1985
|
+
if (pos >= len) {
|
|
1986
|
+
result += text.substring(start, pos);
|
|
1987
|
+
scanError = 2;
|
|
1988
|
+
break;
|
|
1989
|
+
}
|
|
1990
|
+
const ch = text.charCodeAt(pos);
|
|
1991
|
+
if (ch === 34) {
|
|
1992
|
+
result += text.substring(start, pos);
|
|
1993
|
+
pos++;
|
|
1994
|
+
break;
|
|
1995
|
+
}
|
|
1996
|
+
if (ch === 92) {
|
|
1997
|
+
result += text.substring(start, pos);
|
|
1998
|
+
pos++;
|
|
1999
|
+
if (pos >= len) {
|
|
2000
|
+
scanError = 2;
|
|
2001
|
+
break;
|
|
2002
|
+
}
|
|
2003
|
+
const ch2 = text.charCodeAt(pos++);
|
|
2004
|
+
switch (ch2) {
|
|
2005
|
+
case 34:
|
|
2006
|
+
result += '"';
|
|
2007
|
+
break;
|
|
2008
|
+
case 92:
|
|
2009
|
+
result += "\\";
|
|
2010
|
+
break;
|
|
2011
|
+
case 47:
|
|
2012
|
+
result += "/";
|
|
2013
|
+
break;
|
|
2014
|
+
case 98:
|
|
2015
|
+
result += "\b";
|
|
2016
|
+
break;
|
|
2017
|
+
case 102:
|
|
2018
|
+
result += "\f";
|
|
2019
|
+
break;
|
|
2020
|
+
case 110:
|
|
2021
|
+
result += "\n";
|
|
2022
|
+
break;
|
|
2023
|
+
case 114:
|
|
2024
|
+
result += "\r";
|
|
2025
|
+
break;
|
|
2026
|
+
case 116:
|
|
2027
|
+
result += " ";
|
|
2028
|
+
break;
|
|
2029
|
+
case 117:
|
|
2030
|
+
const ch3 = scanHexDigits(4, true);
|
|
2031
|
+
if (ch3 >= 0) {
|
|
2032
|
+
result += String.fromCharCode(ch3);
|
|
2033
|
+
} else {
|
|
2034
|
+
scanError = 4;
|
|
2035
|
+
}
|
|
2036
|
+
break;
|
|
2037
|
+
default:
|
|
2038
|
+
scanError = 5;
|
|
2039
|
+
}
|
|
2040
|
+
start = pos;
|
|
1578
2041
|
continue;
|
|
1579
2042
|
}
|
|
1580
|
-
if (
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
2043
|
+
if (ch >= 0 && ch <= 31) {
|
|
2044
|
+
if (isLineBreak(ch)) {
|
|
2045
|
+
result += text.substring(start, pos);
|
|
2046
|
+
scanError = 2;
|
|
2047
|
+
break;
|
|
2048
|
+
} else {
|
|
2049
|
+
scanError = 6;
|
|
2050
|
+
}
|
|
1586
2051
|
}
|
|
1587
|
-
|
|
2052
|
+
pos++;
|
|
1588
2053
|
}
|
|
1589
|
-
return
|
|
2054
|
+
return result;
|
|
1590
2055
|
}
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
if (
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
2056
|
+
function scanNext() {
|
|
2057
|
+
value = "";
|
|
2058
|
+
scanError = 0;
|
|
2059
|
+
tokenOffset = pos;
|
|
2060
|
+
lineStartOffset = lineNumber;
|
|
2061
|
+
prevTokenLineStartOffset = tokenLineStartOffset;
|
|
2062
|
+
if (pos >= len) {
|
|
2063
|
+
tokenOffset = len;
|
|
2064
|
+
return token = 17;
|
|
2065
|
+
}
|
|
2066
|
+
let code = text.charCodeAt(pos);
|
|
2067
|
+
if (isWhiteSpace(code)) {
|
|
2068
|
+
do {
|
|
2069
|
+
pos++;
|
|
2070
|
+
value += String.fromCharCode(code);
|
|
2071
|
+
code = text.charCodeAt(pos);
|
|
2072
|
+
} while (isWhiteSpace(code));
|
|
2073
|
+
return token = 15;
|
|
2074
|
+
}
|
|
2075
|
+
if (isLineBreak(code)) {
|
|
2076
|
+
pos++;
|
|
2077
|
+
value += String.fromCharCode(code);
|
|
2078
|
+
if (code === 13 && text.charCodeAt(pos) === 10) {
|
|
2079
|
+
pos++;
|
|
2080
|
+
value += "\n";
|
|
2081
|
+
}
|
|
2082
|
+
lineNumber++;
|
|
2083
|
+
tokenLineStartOffset = pos;
|
|
2084
|
+
return token = 14;
|
|
2085
|
+
}
|
|
2086
|
+
switch (code) {
|
|
2087
|
+
// tokens: []{}:,
|
|
2088
|
+
case 123:
|
|
2089
|
+
pos++;
|
|
2090
|
+
return token = 1;
|
|
2091
|
+
case 125:
|
|
2092
|
+
pos++;
|
|
2093
|
+
return token = 2;
|
|
2094
|
+
case 91:
|
|
2095
|
+
pos++;
|
|
2096
|
+
return token = 3;
|
|
2097
|
+
case 93:
|
|
2098
|
+
pos++;
|
|
2099
|
+
return token = 4;
|
|
2100
|
+
case 58:
|
|
2101
|
+
pos++;
|
|
2102
|
+
return token = 6;
|
|
2103
|
+
case 44:
|
|
2104
|
+
pos++;
|
|
2105
|
+
return token = 5;
|
|
2106
|
+
// strings
|
|
2107
|
+
case 34:
|
|
2108
|
+
pos++;
|
|
2109
|
+
value = scanString();
|
|
2110
|
+
return token = 10;
|
|
2111
|
+
// comments
|
|
2112
|
+
case 47:
|
|
2113
|
+
const start = pos - 1;
|
|
2114
|
+
if (text.charCodeAt(pos + 1) === 47) {
|
|
2115
|
+
pos += 2;
|
|
2116
|
+
while (pos < len) {
|
|
2117
|
+
if (isLineBreak(text.charCodeAt(pos))) {
|
|
2118
|
+
break;
|
|
2119
|
+
}
|
|
2120
|
+
pos++;
|
|
2121
|
+
}
|
|
2122
|
+
value = text.substring(start, pos);
|
|
2123
|
+
return token = 12;
|
|
2124
|
+
}
|
|
2125
|
+
if (text.charCodeAt(pos + 1) === 42) {
|
|
2126
|
+
pos += 2;
|
|
2127
|
+
const safeLength = len - 1;
|
|
2128
|
+
let commentClosed = false;
|
|
2129
|
+
while (pos < safeLength) {
|
|
2130
|
+
const ch = text.charCodeAt(pos);
|
|
2131
|
+
if (ch === 42 && text.charCodeAt(pos + 1) === 47) {
|
|
2132
|
+
pos += 2;
|
|
2133
|
+
commentClosed = true;
|
|
2134
|
+
break;
|
|
2135
|
+
}
|
|
2136
|
+
pos++;
|
|
2137
|
+
if (isLineBreak(ch)) {
|
|
2138
|
+
if (ch === 13 && text.charCodeAt(pos) === 10) {
|
|
2139
|
+
pos++;
|
|
2140
|
+
}
|
|
2141
|
+
lineNumber++;
|
|
2142
|
+
tokenLineStartOffset = pos;
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
if (!commentClosed) {
|
|
2146
|
+
pos++;
|
|
2147
|
+
scanError = 1;
|
|
2148
|
+
}
|
|
2149
|
+
value = text.substring(start, pos);
|
|
2150
|
+
return token = 13;
|
|
2151
|
+
}
|
|
2152
|
+
value += String.fromCharCode(code);
|
|
2153
|
+
pos++;
|
|
2154
|
+
return token = 16;
|
|
2155
|
+
// numbers
|
|
2156
|
+
case 45:
|
|
2157
|
+
value += String.fromCharCode(code);
|
|
2158
|
+
pos++;
|
|
2159
|
+
if (pos === len || !isDigit(text.charCodeAt(pos))) {
|
|
2160
|
+
return token = 16;
|
|
2161
|
+
}
|
|
2162
|
+
// found a minus, followed by a number so
|
|
2163
|
+
// we fall through to proceed with scanning
|
|
2164
|
+
// numbers
|
|
2165
|
+
case 48:
|
|
2166
|
+
case 49:
|
|
2167
|
+
case 50:
|
|
2168
|
+
case 51:
|
|
2169
|
+
case 52:
|
|
2170
|
+
case 53:
|
|
2171
|
+
case 54:
|
|
2172
|
+
case 55:
|
|
2173
|
+
case 56:
|
|
2174
|
+
case 57:
|
|
2175
|
+
value += scanNumber();
|
|
2176
|
+
return token = 11;
|
|
2177
|
+
// literals and unknown symbols
|
|
2178
|
+
default:
|
|
2179
|
+
while (pos < len && isUnknownContentCharacter(code)) {
|
|
2180
|
+
pos++;
|
|
2181
|
+
code = text.charCodeAt(pos);
|
|
2182
|
+
}
|
|
2183
|
+
if (tokenOffset !== pos) {
|
|
2184
|
+
value = text.substring(tokenOffset, pos);
|
|
2185
|
+
switch (value) {
|
|
2186
|
+
case "true":
|
|
2187
|
+
return token = 8;
|
|
2188
|
+
case "false":
|
|
2189
|
+
return token = 9;
|
|
2190
|
+
case "null":
|
|
2191
|
+
return token = 7;
|
|
2192
|
+
}
|
|
2193
|
+
return token = 16;
|
|
2194
|
+
}
|
|
2195
|
+
value += String.fromCharCode(code);
|
|
2196
|
+
pos++;
|
|
2197
|
+
return token = 16;
|
|
2198
|
+
}
|
|
1619
2199
|
}
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
2200
|
+
function isUnknownContentCharacter(code) {
|
|
2201
|
+
if (isWhiteSpace(code) || isLineBreak(code)) {
|
|
2202
|
+
return false;
|
|
2203
|
+
}
|
|
2204
|
+
switch (code) {
|
|
2205
|
+
case 125:
|
|
2206
|
+
case 93:
|
|
2207
|
+
case 123:
|
|
2208
|
+
case 91:
|
|
2209
|
+
case 34:
|
|
2210
|
+
case 58:
|
|
2211
|
+
case 44:
|
|
2212
|
+
case 47:
|
|
2213
|
+
return false;
|
|
2214
|
+
}
|
|
2215
|
+
return true;
|
|
1628
2216
|
}
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
2217
|
+
function scanNextNonTrivia() {
|
|
2218
|
+
let result;
|
|
2219
|
+
do {
|
|
2220
|
+
result = scanNext();
|
|
2221
|
+
} while (result >= 12 && result <= 15);
|
|
2222
|
+
return result;
|
|
2223
|
+
}
|
|
2224
|
+
return {
|
|
2225
|
+
setPosition,
|
|
2226
|
+
getPosition: () => pos,
|
|
2227
|
+
scan: ignoreTrivia ? scanNextNonTrivia : scanNext,
|
|
2228
|
+
getToken: () => token,
|
|
2229
|
+
getTokenValue: () => value,
|
|
2230
|
+
getTokenOffset: () => tokenOffset,
|
|
2231
|
+
getTokenLength: () => pos - tokenOffset,
|
|
2232
|
+
getTokenStartLine: () => lineStartOffset,
|
|
2233
|
+
getTokenStartCharacter: () => tokenOffset - prevTokenLineStartOffset,
|
|
2234
|
+
getTokenError: () => scanError
|
|
2235
|
+
};
|
|
1633
2236
|
}
|
|
1634
|
-
function
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
2237
|
+
function isWhiteSpace(ch) {
|
|
2238
|
+
return ch === 32 || ch === 9;
|
|
2239
|
+
}
|
|
2240
|
+
function isLineBreak(ch) {
|
|
2241
|
+
return ch === 10 || ch === 13;
|
|
2242
|
+
}
|
|
2243
|
+
function isDigit(ch) {
|
|
2244
|
+
return ch >= 48 && ch <= 57;
|
|
2245
|
+
}
|
|
2246
|
+
var CharacterCodes;
|
|
2247
|
+
(function(CharacterCodes2) {
|
|
2248
|
+
CharacterCodes2[CharacterCodes2["lineFeed"] = 10] = "lineFeed";
|
|
2249
|
+
CharacterCodes2[CharacterCodes2["carriageReturn"] = 13] = "carriageReturn";
|
|
2250
|
+
CharacterCodes2[CharacterCodes2["space"] = 32] = "space";
|
|
2251
|
+
CharacterCodes2[CharacterCodes2["_0"] = 48] = "_0";
|
|
2252
|
+
CharacterCodes2[CharacterCodes2["_1"] = 49] = "_1";
|
|
2253
|
+
CharacterCodes2[CharacterCodes2["_2"] = 50] = "_2";
|
|
2254
|
+
CharacterCodes2[CharacterCodes2["_3"] = 51] = "_3";
|
|
2255
|
+
CharacterCodes2[CharacterCodes2["_4"] = 52] = "_4";
|
|
2256
|
+
CharacterCodes2[CharacterCodes2["_5"] = 53] = "_5";
|
|
2257
|
+
CharacterCodes2[CharacterCodes2["_6"] = 54] = "_6";
|
|
2258
|
+
CharacterCodes2[CharacterCodes2["_7"] = 55] = "_7";
|
|
2259
|
+
CharacterCodes2[CharacterCodes2["_8"] = 56] = "_8";
|
|
2260
|
+
CharacterCodes2[CharacterCodes2["_9"] = 57] = "_9";
|
|
2261
|
+
CharacterCodes2[CharacterCodes2["a"] = 97] = "a";
|
|
2262
|
+
CharacterCodes2[CharacterCodes2["b"] = 98] = "b";
|
|
2263
|
+
CharacterCodes2[CharacterCodes2["c"] = 99] = "c";
|
|
2264
|
+
CharacterCodes2[CharacterCodes2["d"] = 100] = "d";
|
|
2265
|
+
CharacterCodes2[CharacterCodes2["e"] = 101] = "e";
|
|
2266
|
+
CharacterCodes2[CharacterCodes2["f"] = 102] = "f";
|
|
2267
|
+
CharacterCodes2[CharacterCodes2["g"] = 103] = "g";
|
|
2268
|
+
CharacterCodes2[CharacterCodes2["h"] = 104] = "h";
|
|
2269
|
+
CharacterCodes2[CharacterCodes2["i"] = 105] = "i";
|
|
2270
|
+
CharacterCodes2[CharacterCodes2["j"] = 106] = "j";
|
|
2271
|
+
CharacterCodes2[CharacterCodes2["k"] = 107] = "k";
|
|
2272
|
+
CharacterCodes2[CharacterCodes2["l"] = 108] = "l";
|
|
2273
|
+
CharacterCodes2[CharacterCodes2["m"] = 109] = "m";
|
|
2274
|
+
CharacterCodes2[CharacterCodes2["n"] = 110] = "n";
|
|
2275
|
+
CharacterCodes2[CharacterCodes2["o"] = 111] = "o";
|
|
2276
|
+
CharacterCodes2[CharacterCodes2["p"] = 112] = "p";
|
|
2277
|
+
CharacterCodes2[CharacterCodes2["q"] = 113] = "q";
|
|
2278
|
+
CharacterCodes2[CharacterCodes2["r"] = 114] = "r";
|
|
2279
|
+
CharacterCodes2[CharacterCodes2["s"] = 115] = "s";
|
|
2280
|
+
CharacterCodes2[CharacterCodes2["t"] = 116] = "t";
|
|
2281
|
+
CharacterCodes2[CharacterCodes2["u"] = 117] = "u";
|
|
2282
|
+
CharacterCodes2[CharacterCodes2["v"] = 118] = "v";
|
|
2283
|
+
CharacterCodes2[CharacterCodes2["w"] = 119] = "w";
|
|
2284
|
+
CharacterCodes2[CharacterCodes2["x"] = 120] = "x";
|
|
2285
|
+
CharacterCodes2[CharacterCodes2["y"] = 121] = "y";
|
|
2286
|
+
CharacterCodes2[CharacterCodes2["z"] = 122] = "z";
|
|
2287
|
+
CharacterCodes2[CharacterCodes2["A"] = 65] = "A";
|
|
2288
|
+
CharacterCodes2[CharacterCodes2["B"] = 66] = "B";
|
|
2289
|
+
CharacterCodes2[CharacterCodes2["C"] = 67] = "C";
|
|
2290
|
+
CharacterCodes2[CharacterCodes2["D"] = 68] = "D";
|
|
2291
|
+
CharacterCodes2[CharacterCodes2["E"] = 69] = "E";
|
|
2292
|
+
CharacterCodes2[CharacterCodes2["F"] = 70] = "F";
|
|
2293
|
+
CharacterCodes2[CharacterCodes2["G"] = 71] = "G";
|
|
2294
|
+
CharacterCodes2[CharacterCodes2["H"] = 72] = "H";
|
|
2295
|
+
CharacterCodes2[CharacterCodes2["I"] = 73] = "I";
|
|
2296
|
+
CharacterCodes2[CharacterCodes2["J"] = 74] = "J";
|
|
2297
|
+
CharacterCodes2[CharacterCodes2["K"] = 75] = "K";
|
|
2298
|
+
CharacterCodes2[CharacterCodes2["L"] = 76] = "L";
|
|
2299
|
+
CharacterCodes2[CharacterCodes2["M"] = 77] = "M";
|
|
2300
|
+
CharacterCodes2[CharacterCodes2["N"] = 78] = "N";
|
|
2301
|
+
CharacterCodes2[CharacterCodes2["O"] = 79] = "O";
|
|
2302
|
+
CharacterCodes2[CharacterCodes2["P"] = 80] = "P";
|
|
2303
|
+
CharacterCodes2[CharacterCodes2["Q"] = 81] = "Q";
|
|
2304
|
+
CharacterCodes2[CharacterCodes2["R"] = 82] = "R";
|
|
2305
|
+
CharacterCodes2[CharacterCodes2["S"] = 83] = "S";
|
|
2306
|
+
CharacterCodes2[CharacterCodes2["T"] = 84] = "T";
|
|
2307
|
+
CharacterCodes2[CharacterCodes2["U"] = 85] = "U";
|
|
2308
|
+
CharacterCodes2[CharacterCodes2["V"] = 86] = "V";
|
|
2309
|
+
CharacterCodes2[CharacterCodes2["W"] = 87] = "W";
|
|
2310
|
+
CharacterCodes2[CharacterCodes2["X"] = 88] = "X";
|
|
2311
|
+
CharacterCodes2[CharacterCodes2["Y"] = 89] = "Y";
|
|
2312
|
+
CharacterCodes2[CharacterCodes2["Z"] = 90] = "Z";
|
|
2313
|
+
CharacterCodes2[CharacterCodes2["asterisk"] = 42] = "asterisk";
|
|
2314
|
+
CharacterCodes2[CharacterCodes2["backslash"] = 92] = "backslash";
|
|
2315
|
+
CharacterCodes2[CharacterCodes2["closeBrace"] = 125] = "closeBrace";
|
|
2316
|
+
CharacterCodes2[CharacterCodes2["closeBracket"] = 93] = "closeBracket";
|
|
2317
|
+
CharacterCodes2[CharacterCodes2["colon"] = 58] = "colon";
|
|
2318
|
+
CharacterCodes2[CharacterCodes2["comma"] = 44] = "comma";
|
|
2319
|
+
CharacterCodes2[CharacterCodes2["dot"] = 46] = "dot";
|
|
2320
|
+
CharacterCodes2[CharacterCodes2["doubleQuote"] = 34] = "doubleQuote";
|
|
2321
|
+
CharacterCodes2[CharacterCodes2["minus"] = 45] = "minus";
|
|
2322
|
+
CharacterCodes2[CharacterCodes2["openBrace"] = 123] = "openBrace";
|
|
2323
|
+
CharacterCodes2[CharacterCodes2["openBracket"] = 91] = "openBracket";
|
|
2324
|
+
CharacterCodes2[CharacterCodes2["plus"] = 43] = "plus";
|
|
2325
|
+
CharacterCodes2[CharacterCodes2["slash"] = 47] = "slash";
|
|
2326
|
+
CharacterCodes2[CharacterCodes2["formFeed"] = 12] = "formFeed";
|
|
2327
|
+
CharacterCodes2[CharacterCodes2["tab"] = 9] = "tab";
|
|
2328
|
+
})(CharacterCodes || (CharacterCodes = {}));
|
|
2329
|
+
|
|
2330
|
+
// ../../node_modules/.pnpm/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/string-intern.js
|
|
2331
|
+
var cachedSpaces = new Array(20).fill(0).map((_, index) => {
|
|
2332
|
+
return " ".repeat(index);
|
|
2333
|
+
});
|
|
2334
|
+
var maxCachedValues = 200;
|
|
2335
|
+
var cachedBreakLinesWithSpaces = {
|
|
2336
|
+
" ": {
|
|
2337
|
+
"\n": new Array(maxCachedValues).fill(0).map((_, index) => {
|
|
2338
|
+
return "\n" + " ".repeat(index);
|
|
2339
|
+
}),
|
|
2340
|
+
"\r": new Array(maxCachedValues).fill(0).map((_, index) => {
|
|
2341
|
+
return "\r" + " ".repeat(index);
|
|
2342
|
+
}),
|
|
2343
|
+
"\r\n": new Array(maxCachedValues).fill(0).map((_, index) => {
|
|
2344
|
+
return "\r\n" + " ".repeat(index);
|
|
2345
|
+
})
|
|
2346
|
+
},
|
|
2347
|
+
" ": {
|
|
2348
|
+
"\n": new Array(maxCachedValues).fill(0).map((_, index) => {
|
|
2349
|
+
return "\n" + " ".repeat(index);
|
|
2350
|
+
}),
|
|
2351
|
+
"\r": new Array(maxCachedValues).fill(0).map((_, index) => {
|
|
2352
|
+
return "\r" + " ".repeat(index);
|
|
2353
|
+
}),
|
|
2354
|
+
"\r\n": new Array(maxCachedValues).fill(0).map((_, index) => {
|
|
2355
|
+
return "\r\n" + " ".repeat(index);
|
|
2356
|
+
})
|
|
2357
|
+
}
|
|
2358
|
+
};
|
|
2359
|
+
|
|
2360
|
+
// ../../node_modules/.pnpm/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/parser.js
|
|
2361
|
+
var ParseOptions;
|
|
2362
|
+
(function(ParseOptions2) {
|
|
2363
|
+
ParseOptions2.DEFAULT = {
|
|
2364
|
+
allowTrailingComma: false
|
|
2365
|
+
};
|
|
2366
|
+
})(ParseOptions || (ParseOptions = {}));
|
|
2367
|
+
function parseTree(text, errors = [], options = ParseOptions.DEFAULT) {
|
|
2368
|
+
let currentParent = { type: "array", offset: -1, length: -1, children: [], parent: void 0 };
|
|
2369
|
+
function ensurePropertyComplete(endOffset) {
|
|
2370
|
+
if (currentParent.type === "property") {
|
|
2371
|
+
currentParent.length = endOffset - currentParent.offset;
|
|
2372
|
+
currentParent = currentParent.parent;
|
|
1649
2373
|
}
|
|
1650
|
-
return `${text.slice(0, last.valueEnd)},${eol}${indent}"workspaces": [${SHARED_TYPES_ENTRY}]${text.slice(last.valueEnd)}`;
|
|
1651
2374
|
}
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
if (text[workspaces.valueStart] !== "[")
|
|
1656
|
-
return null;
|
|
1657
|
-
return appendToArray(text, workspaces.valueStart, workspaces.valueEnd, eol);
|
|
2375
|
+
function onValue(valueNode) {
|
|
2376
|
+
currentParent.children.push(valueNode);
|
|
2377
|
+
return valueNode;
|
|
1658
2378
|
}
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
2379
|
+
const visitor = {
|
|
2380
|
+
onObjectBegin: (offset) => {
|
|
2381
|
+
currentParent = onValue({ type: "object", offset, length: -1, parent: currentParent, children: [] });
|
|
2382
|
+
},
|
|
2383
|
+
onObjectProperty: (name, offset, length) => {
|
|
2384
|
+
currentParent = onValue({ type: "property", offset, length: -1, parent: currentParent, children: [] });
|
|
2385
|
+
currentParent.children.push({ type: "string", value: name, offset, length, parent: currentParent });
|
|
2386
|
+
},
|
|
2387
|
+
onObjectEnd: (offset, length) => {
|
|
2388
|
+
ensurePropertyComplete(offset + length);
|
|
2389
|
+
currentParent.length = offset + length - currentParent.offset;
|
|
2390
|
+
currentParent = currentParent.parent;
|
|
2391
|
+
ensurePropertyComplete(offset + length);
|
|
2392
|
+
},
|
|
2393
|
+
onArrayBegin: (offset, length) => {
|
|
2394
|
+
currentParent = onValue({ type: "array", offset, length: -1, parent: currentParent, children: [] });
|
|
2395
|
+
},
|
|
2396
|
+
onArrayEnd: (offset, length) => {
|
|
2397
|
+
currentParent.length = offset + length - currentParent.offset;
|
|
2398
|
+
currentParent = currentParent.parent;
|
|
2399
|
+
ensurePropertyComplete(offset + length);
|
|
2400
|
+
},
|
|
2401
|
+
onLiteralValue: (value, offset, length) => {
|
|
2402
|
+
onValue({ type: getNodeType(value), offset, length, parent: currentParent, value });
|
|
2403
|
+
ensurePropertyComplete(offset + length);
|
|
2404
|
+
},
|
|
2405
|
+
onSeparator: (sep, offset, length) => {
|
|
2406
|
+
if (currentParent.type === "property") {
|
|
2407
|
+
if (sep === ":") {
|
|
2408
|
+
currentParent.colonOffset = offset;
|
|
2409
|
+
} else if (sep === ",") {
|
|
2410
|
+
ensurePropertyComplete(offset);
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
},
|
|
2414
|
+
onError: (error, offset, length) => {
|
|
2415
|
+
errors.push({ error, offset, length });
|
|
2416
|
+
}
|
|
2417
|
+
};
|
|
2418
|
+
visit(text, visitor, options);
|
|
2419
|
+
const result = currentParent.children[0];
|
|
2420
|
+
if (result) {
|
|
2421
|
+
delete result.parent;
|
|
2422
|
+
}
|
|
2423
|
+
return result;
|
|
1665
2424
|
}
|
|
1666
|
-
function
|
|
1667
|
-
if (
|
|
1668
|
-
return
|
|
2425
|
+
function findNodeAtLocation(root, path) {
|
|
2426
|
+
if (!root) {
|
|
2427
|
+
return void 0;
|
|
1669
2428
|
}
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
2429
|
+
let node = root;
|
|
2430
|
+
for (let segment of path) {
|
|
2431
|
+
if (typeof segment === "string") {
|
|
2432
|
+
if (node.type !== "object" || !Array.isArray(node.children)) {
|
|
2433
|
+
return void 0;
|
|
2434
|
+
}
|
|
2435
|
+
let found = false;
|
|
2436
|
+
for (const propertyNode of node.children) {
|
|
2437
|
+
if (Array.isArray(propertyNode.children) && propertyNode.children[0].value === segment && propertyNode.children.length === 2) {
|
|
2438
|
+
node = propertyNode.children[1];
|
|
2439
|
+
found = true;
|
|
2440
|
+
break;
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
if (!found) {
|
|
2444
|
+
return void 0;
|
|
2445
|
+
}
|
|
1680
2446
|
} else {
|
|
1681
|
-
|
|
2447
|
+
const index = segment;
|
|
2448
|
+
if (node.type !== "array" || index < 0 || !Array.isArray(node.children) || index >= node.children.length) {
|
|
2449
|
+
return void 0;
|
|
2450
|
+
}
|
|
2451
|
+
node = node.children[index];
|
|
1682
2452
|
}
|
|
1683
2453
|
}
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
2454
|
+
return node;
|
|
2455
|
+
}
|
|
2456
|
+
function visit(text, visitor, options = ParseOptions.DEFAULT) {
|
|
2457
|
+
const _scanner = createScanner(text, false);
|
|
2458
|
+
const _jsonPath = [];
|
|
2459
|
+
let suppressedCallbacks = 0;
|
|
2460
|
+
function toNoArgVisit(visitFunction) {
|
|
2461
|
+
return visitFunction ? () => suppressedCallbacks === 0 && visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter()) : () => true;
|
|
2462
|
+
}
|
|
2463
|
+
function toOneArgVisit(visitFunction) {
|
|
2464
|
+
return visitFunction ? (arg) => suppressedCallbacks === 0 && visitFunction(arg, _scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter()) : () => true;
|
|
2465
|
+
}
|
|
2466
|
+
function toOneArgVisitWithPath(visitFunction) {
|
|
2467
|
+
return visitFunction ? (arg) => suppressedCallbacks === 0 && visitFunction(arg, _scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter(), () => _jsonPath.slice()) : () => true;
|
|
2468
|
+
}
|
|
2469
|
+
function toBeginVisit(visitFunction) {
|
|
2470
|
+
return visitFunction ? () => {
|
|
2471
|
+
if (suppressedCallbacks > 0) {
|
|
2472
|
+
suppressedCallbacks++;
|
|
2473
|
+
} else {
|
|
2474
|
+
let cbReturn = visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter(), () => _jsonPath.slice());
|
|
2475
|
+
if (cbReturn === false) {
|
|
2476
|
+
suppressedCallbacks = 1;
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
} : () => true;
|
|
2480
|
+
}
|
|
2481
|
+
function toEndVisit(visitFunction) {
|
|
2482
|
+
return visitFunction ? () => {
|
|
2483
|
+
if (suppressedCallbacks > 0) {
|
|
2484
|
+
suppressedCallbacks--;
|
|
2485
|
+
}
|
|
2486
|
+
if (suppressedCallbacks === 0) {
|
|
2487
|
+
visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter());
|
|
2488
|
+
}
|
|
2489
|
+
} : () => true;
|
|
2490
|
+
}
|
|
2491
|
+
const onObjectBegin = toBeginVisit(visitor.onObjectBegin), onObjectProperty = toOneArgVisitWithPath(visitor.onObjectProperty), onObjectEnd = toEndVisit(visitor.onObjectEnd), onArrayBegin = toBeginVisit(visitor.onArrayBegin), onArrayEnd = toEndVisit(visitor.onArrayEnd), onLiteralValue = toOneArgVisitWithPath(visitor.onLiteralValue), onSeparator = toOneArgVisit(visitor.onSeparator), onComment = toNoArgVisit(visitor.onComment), onError = toOneArgVisit(visitor.onError);
|
|
2492
|
+
const disallowComments = options && options.disallowComments;
|
|
2493
|
+
const allowTrailingComma = options && options.allowTrailingComma;
|
|
2494
|
+
function scanNext() {
|
|
2495
|
+
while (true) {
|
|
2496
|
+
const token = _scanner.scan();
|
|
2497
|
+
switch (_scanner.getTokenError()) {
|
|
2498
|
+
case 4:
|
|
2499
|
+
handleError(
|
|
2500
|
+
14
|
|
2501
|
+
/* ParseErrorCode.InvalidUnicode */
|
|
2502
|
+
);
|
|
2503
|
+
break;
|
|
2504
|
+
case 5:
|
|
2505
|
+
handleError(
|
|
2506
|
+
15
|
|
2507
|
+
/* ParseErrorCode.InvalidEscapeCharacter */
|
|
2508
|
+
);
|
|
2509
|
+
break;
|
|
2510
|
+
case 3:
|
|
2511
|
+
handleError(
|
|
2512
|
+
13
|
|
2513
|
+
/* ParseErrorCode.UnexpectedEndOfNumber */
|
|
2514
|
+
);
|
|
2515
|
+
break;
|
|
2516
|
+
case 1:
|
|
2517
|
+
if (!disallowComments) {
|
|
2518
|
+
handleError(
|
|
2519
|
+
11
|
|
2520
|
+
/* ParseErrorCode.UnexpectedEndOfComment */
|
|
2521
|
+
);
|
|
2522
|
+
}
|
|
2523
|
+
break;
|
|
2524
|
+
case 2:
|
|
2525
|
+
handleError(
|
|
2526
|
+
12
|
|
2527
|
+
/* ParseErrorCode.UnexpectedEndOfString */
|
|
2528
|
+
);
|
|
2529
|
+
break;
|
|
2530
|
+
case 6:
|
|
2531
|
+
handleError(
|
|
2532
|
+
16
|
|
2533
|
+
/* ParseErrorCode.InvalidCharacter */
|
|
2534
|
+
);
|
|
2535
|
+
break;
|
|
2536
|
+
}
|
|
2537
|
+
switch (token) {
|
|
2538
|
+
case 12:
|
|
2539
|
+
case 13:
|
|
2540
|
+
if (disallowComments) {
|
|
2541
|
+
handleError(
|
|
2542
|
+
10
|
|
2543
|
+
/* ParseErrorCode.InvalidCommentToken */
|
|
2544
|
+
);
|
|
2545
|
+
} else {
|
|
2546
|
+
onComment();
|
|
2547
|
+
}
|
|
2548
|
+
break;
|
|
2549
|
+
case 16:
|
|
2550
|
+
handleError(
|
|
2551
|
+
1
|
|
2552
|
+
/* ParseErrorCode.InvalidSymbol */
|
|
2553
|
+
);
|
|
2554
|
+
break;
|
|
2555
|
+
case 15:
|
|
2556
|
+
case 14:
|
|
2557
|
+
break;
|
|
2558
|
+
default:
|
|
2559
|
+
return token;
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
function handleError(error, skipUntilAfter = [], skipUntil = []) {
|
|
2564
|
+
onError(error);
|
|
2565
|
+
if (skipUntilAfter.length + skipUntil.length > 0) {
|
|
2566
|
+
let token = _scanner.getToken();
|
|
2567
|
+
while (token !== 17) {
|
|
2568
|
+
if (skipUntilAfter.indexOf(token) !== -1) {
|
|
2569
|
+
scanNext();
|
|
2570
|
+
break;
|
|
2571
|
+
} else if (skipUntil.indexOf(token) !== -1) {
|
|
2572
|
+
break;
|
|
2573
|
+
}
|
|
2574
|
+
token = scanNext();
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
function parseString(isValue) {
|
|
2579
|
+
const value = _scanner.getTokenValue();
|
|
2580
|
+
if (isValue) {
|
|
2581
|
+
onLiteralValue(value);
|
|
2582
|
+
} else {
|
|
2583
|
+
onObjectProperty(value);
|
|
2584
|
+
_jsonPath.push(value);
|
|
2585
|
+
}
|
|
2586
|
+
scanNext();
|
|
2587
|
+
return true;
|
|
2588
|
+
}
|
|
2589
|
+
function parseLiteral() {
|
|
2590
|
+
switch (_scanner.getToken()) {
|
|
2591
|
+
case 11:
|
|
2592
|
+
const tokenValue = _scanner.getTokenValue();
|
|
2593
|
+
let value = Number(tokenValue);
|
|
2594
|
+
if (isNaN(value)) {
|
|
2595
|
+
handleError(
|
|
2596
|
+
2
|
|
2597
|
+
/* ParseErrorCode.InvalidNumberFormat */
|
|
2598
|
+
);
|
|
2599
|
+
value = 0;
|
|
2600
|
+
}
|
|
2601
|
+
onLiteralValue(value);
|
|
2602
|
+
break;
|
|
2603
|
+
case 7:
|
|
2604
|
+
onLiteralValue(null);
|
|
2605
|
+
break;
|
|
2606
|
+
case 8:
|
|
2607
|
+
onLiteralValue(true);
|
|
2608
|
+
break;
|
|
2609
|
+
case 9:
|
|
2610
|
+
onLiteralValue(false);
|
|
2611
|
+
break;
|
|
2612
|
+
default:
|
|
2613
|
+
return false;
|
|
2614
|
+
}
|
|
2615
|
+
scanNext();
|
|
2616
|
+
return true;
|
|
2617
|
+
}
|
|
2618
|
+
function parseProperty() {
|
|
2619
|
+
if (_scanner.getToken() !== 10) {
|
|
2620
|
+
handleError(3, [], [
|
|
2621
|
+
2,
|
|
2622
|
+
5
|
|
2623
|
+
/* SyntaxKind.CommaToken */
|
|
2624
|
+
]);
|
|
2625
|
+
return false;
|
|
2626
|
+
}
|
|
2627
|
+
parseString(false);
|
|
2628
|
+
if (_scanner.getToken() === 6) {
|
|
2629
|
+
onSeparator(":");
|
|
2630
|
+
scanNext();
|
|
2631
|
+
if (!parseValue()) {
|
|
2632
|
+
handleError(4, [], [
|
|
2633
|
+
2,
|
|
2634
|
+
5
|
|
2635
|
+
/* SyntaxKind.CommaToken */
|
|
2636
|
+
]);
|
|
2637
|
+
}
|
|
2638
|
+
} else {
|
|
2639
|
+
handleError(5, [], [
|
|
2640
|
+
2,
|
|
2641
|
+
5
|
|
2642
|
+
/* SyntaxKind.CommaToken */
|
|
2643
|
+
]);
|
|
2644
|
+
}
|
|
2645
|
+
_jsonPath.pop();
|
|
2646
|
+
return true;
|
|
2647
|
+
}
|
|
2648
|
+
function parseObject() {
|
|
2649
|
+
onObjectBegin();
|
|
2650
|
+
scanNext();
|
|
2651
|
+
let needsComma = false;
|
|
2652
|
+
while (_scanner.getToken() !== 2 && _scanner.getToken() !== 17) {
|
|
2653
|
+
if (_scanner.getToken() === 5) {
|
|
2654
|
+
if (!needsComma) {
|
|
2655
|
+
handleError(4, [], []);
|
|
2656
|
+
}
|
|
2657
|
+
onSeparator(",");
|
|
2658
|
+
scanNext();
|
|
2659
|
+
if (_scanner.getToken() === 2 && allowTrailingComma) {
|
|
2660
|
+
break;
|
|
2661
|
+
}
|
|
2662
|
+
} else if (needsComma) {
|
|
2663
|
+
handleError(6, [], []);
|
|
2664
|
+
}
|
|
2665
|
+
if (!parseProperty()) {
|
|
2666
|
+
handleError(4, [], [
|
|
2667
|
+
2,
|
|
2668
|
+
5
|
|
2669
|
+
/* SyntaxKind.CommaToken */
|
|
2670
|
+
]);
|
|
2671
|
+
}
|
|
2672
|
+
needsComma = true;
|
|
2673
|
+
}
|
|
2674
|
+
onObjectEnd();
|
|
2675
|
+
if (_scanner.getToken() !== 2) {
|
|
2676
|
+
handleError(7, [
|
|
2677
|
+
2
|
|
2678
|
+
/* SyntaxKind.CloseBraceToken */
|
|
2679
|
+
], []);
|
|
2680
|
+
} else {
|
|
2681
|
+
scanNext();
|
|
2682
|
+
}
|
|
2683
|
+
return true;
|
|
2684
|
+
}
|
|
2685
|
+
function parseArray() {
|
|
2686
|
+
onArrayBegin();
|
|
2687
|
+
scanNext();
|
|
2688
|
+
let isFirstElement = true;
|
|
2689
|
+
let needsComma = false;
|
|
2690
|
+
while (_scanner.getToken() !== 4 && _scanner.getToken() !== 17) {
|
|
2691
|
+
if (_scanner.getToken() === 5) {
|
|
2692
|
+
if (!needsComma) {
|
|
2693
|
+
handleError(4, [], []);
|
|
2694
|
+
}
|
|
2695
|
+
onSeparator(",");
|
|
2696
|
+
scanNext();
|
|
2697
|
+
if (_scanner.getToken() === 4 && allowTrailingComma) {
|
|
2698
|
+
break;
|
|
2699
|
+
}
|
|
2700
|
+
} else if (needsComma) {
|
|
2701
|
+
handleError(6, [], []);
|
|
2702
|
+
}
|
|
2703
|
+
if (isFirstElement) {
|
|
2704
|
+
_jsonPath.push(0);
|
|
2705
|
+
isFirstElement = false;
|
|
2706
|
+
} else {
|
|
2707
|
+
_jsonPath[_jsonPath.length - 1]++;
|
|
2708
|
+
}
|
|
2709
|
+
if (!parseValue()) {
|
|
2710
|
+
handleError(4, [], [
|
|
2711
|
+
4,
|
|
2712
|
+
5
|
|
2713
|
+
/* SyntaxKind.CommaToken */
|
|
2714
|
+
]);
|
|
2715
|
+
}
|
|
2716
|
+
needsComma = true;
|
|
2717
|
+
}
|
|
2718
|
+
onArrayEnd();
|
|
2719
|
+
if (!isFirstElement) {
|
|
2720
|
+
_jsonPath.pop();
|
|
2721
|
+
}
|
|
2722
|
+
if (_scanner.getToken() !== 4) {
|
|
2723
|
+
handleError(8, [
|
|
2724
|
+
4
|
|
2725
|
+
/* SyntaxKind.CloseBracketToken */
|
|
2726
|
+
], []);
|
|
2727
|
+
} else {
|
|
2728
|
+
scanNext();
|
|
2729
|
+
}
|
|
2730
|
+
return true;
|
|
2731
|
+
}
|
|
2732
|
+
function parseValue() {
|
|
2733
|
+
switch (_scanner.getToken()) {
|
|
2734
|
+
case 3:
|
|
2735
|
+
return parseArray();
|
|
2736
|
+
case 1:
|
|
2737
|
+
return parseObject();
|
|
2738
|
+
case 10:
|
|
2739
|
+
return parseString(true);
|
|
2740
|
+
default:
|
|
2741
|
+
return parseLiteral();
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
scanNext();
|
|
2745
|
+
if (_scanner.getToken() === 17) {
|
|
2746
|
+
if (options.allowEmptyContent) {
|
|
2747
|
+
return true;
|
|
2748
|
+
}
|
|
2749
|
+
handleError(4, [], []);
|
|
2750
|
+
return false;
|
|
2751
|
+
}
|
|
2752
|
+
if (!parseValue()) {
|
|
2753
|
+
handleError(4, [], []);
|
|
2754
|
+
return false;
|
|
2755
|
+
}
|
|
2756
|
+
if (_scanner.getToken() !== 17) {
|
|
2757
|
+
handleError(9, [], []);
|
|
2758
|
+
}
|
|
2759
|
+
return true;
|
|
2760
|
+
}
|
|
2761
|
+
function getNodeType(value) {
|
|
2762
|
+
switch (typeof value) {
|
|
2763
|
+
case "boolean":
|
|
2764
|
+
return "boolean";
|
|
2765
|
+
case "number":
|
|
2766
|
+
return "number";
|
|
2767
|
+
case "string":
|
|
2768
|
+
return "string";
|
|
2769
|
+
case "object": {
|
|
2770
|
+
if (!value) {
|
|
2771
|
+
return "null";
|
|
2772
|
+
} else if (Array.isArray(value)) {
|
|
2773
|
+
return "array";
|
|
2774
|
+
}
|
|
2775
|
+
return "object";
|
|
2776
|
+
}
|
|
2777
|
+
default:
|
|
2778
|
+
return "null";
|
|
2779
|
+
}
|
|
1694
2780
|
}
|
|
1695
2781
|
|
|
1696
|
-
//
|
|
1697
|
-
|
|
2782
|
+
// ../../node_modules/.pnpm/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/main.js
|
|
2783
|
+
var ScanError;
|
|
2784
|
+
(function(ScanError2) {
|
|
2785
|
+
ScanError2[ScanError2["None"] = 0] = "None";
|
|
2786
|
+
ScanError2[ScanError2["UnexpectedEndOfComment"] = 1] = "UnexpectedEndOfComment";
|
|
2787
|
+
ScanError2[ScanError2["UnexpectedEndOfString"] = 2] = "UnexpectedEndOfString";
|
|
2788
|
+
ScanError2[ScanError2["UnexpectedEndOfNumber"] = 3] = "UnexpectedEndOfNumber";
|
|
2789
|
+
ScanError2[ScanError2["InvalidUnicode"] = 4] = "InvalidUnicode";
|
|
2790
|
+
ScanError2[ScanError2["InvalidEscapeCharacter"] = 5] = "InvalidEscapeCharacter";
|
|
2791
|
+
ScanError2[ScanError2["InvalidCharacter"] = 6] = "InvalidCharacter";
|
|
2792
|
+
})(ScanError || (ScanError = {}));
|
|
2793
|
+
var SyntaxKind;
|
|
2794
|
+
(function(SyntaxKind2) {
|
|
2795
|
+
SyntaxKind2[SyntaxKind2["OpenBraceToken"] = 1] = "OpenBraceToken";
|
|
2796
|
+
SyntaxKind2[SyntaxKind2["CloseBraceToken"] = 2] = "CloseBraceToken";
|
|
2797
|
+
SyntaxKind2[SyntaxKind2["OpenBracketToken"] = 3] = "OpenBracketToken";
|
|
2798
|
+
SyntaxKind2[SyntaxKind2["CloseBracketToken"] = 4] = "CloseBracketToken";
|
|
2799
|
+
SyntaxKind2[SyntaxKind2["CommaToken"] = 5] = "CommaToken";
|
|
2800
|
+
SyntaxKind2[SyntaxKind2["ColonToken"] = 6] = "ColonToken";
|
|
2801
|
+
SyntaxKind2[SyntaxKind2["NullKeyword"] = 7] = "NullKeyword";
|
|
2802
|
+
SyntaxKind2[SyntaxKind2["TrueKeyword"] = 8] = "TrueKeyword";
|
|
2803
|
+
SyntaxKind2[SyntaxKind2["FalseKeyword"] = 9] = "FalseKeyword";
|
|
2804
|
+
SyntaxKind2[SyntaxKind2["StringLiteral"] = 10] = "StringLiteral";
|
|
2805
|
+
SyntaxKind2[SyntaxKind2["NumericLiteral"] = 11] = "NumericLiteral";
|
|
2806
|
+
SyntaxKind2[SyntaxKind2["LineCommentTrivia"] = 12] = "LineCommentTrivia";
|
|
2807
|
+
SyntaxKind2[SyntaxKind2["BlockCommentTrivia"] = 13] = "BlockCommentTrivia";
|
|
2808
|
+
SyntaxKind2[SyntaxKind2["LineBreakTrivia"] = 14] = "LineBreakTrivia";
|
|
2809
|
+
SyntaxKind2[SyntaxKind2["Trivia"] = 15] = "Trivia";
|
|
2810
|
+
SyntaxKind2[SyntaxKind2["Unknown"] = 16] = "Unknown";
|
|
2811
|
+
SyntaxKind2[SyntaxKind2["EOF"] = 17] = "EOF";
|
|
2812
|
+
})(SyntaxKind || (SyntaxKind = {}));
|
|
2813
|
+
var parseTree2 = parseTree;
|
|
2814
|
+
var findNodeAtLocation2 = findNodeAtLocation;
|
|
2815
|
+
var ParseErrorCode;
|
|
2816
|
+
(function(ParseErrorCode2) {
|
|
2817
|
+
ParseErrorCode2[ParseErrorCode2["InvalidSymbol"] = 1] = "InvalidSymbol";
|
|
2818
|
+
ParseErrorCode2[ParseErrorCode2["InvalidNumberFormat"] = 2] = "InvalidNumberFormat";
|
|
2819
|
+
ParseErrorCode2[ParseErrorCode2["PropertyNameExpected"] = 3] = "PropertyNameExpected";
|
|
2820
|
+
ParseErrorCode2[ParseErrorCode2["ValueExpected"] = 4] = "ValueExpected";
|
|
2821
|
+
ParseErrorCode2[ParseErrorCode2["ColonExpected"] = 5] = "ColonExpected";
|
|
2822
|
+
ParseErrorCode2[ParseErrorCode2["CommaExpected"] = 6] = "CommaExpected";
|
|
2823
|
+
ParseErrorCode2[ParseErrorCode2["CloseBraceExpected"] = 7] = "CloseBraceExpected";
|
|
2824
|
+
ParseErrorCode2[ParseErrorCode2["CloseBracketExpected"] = 8] = "CloseBracketExpected";
|
|
2825
|
+
ParseErrorCode2[ParseErrorCode2["EndOfFileExpected"] = 9] = "EndOfFileExpected";
|
|
2826
|
+
ParseErrorCode2[ParseErrorCode2["InvalidCommentToken"] = 10] = "InvalidCommentToken";
|
|
2827
|
+
ParseErrorCode2[ParseErrorCode2["UnexpectedEndOfComment"] = 11] = "UnexpectedEndOfComment";
|
|
2828
|
+
ParseErrorCode2[ParseErrorCode2["UnexpectedEndOfString"] = 12] = "UnexpectedEndOfString";
|
|
2829
|
+
ParseErrorCode2[ParseErrorCode2["UnexpectedEndOfNumber"] = 13] = "UnexpectedEndOfNumber";
|
|
2830
|
+
ParseErrorCode2[ParseErrorCode2["InvalidUnicode"] = 14] = "InvalidUnicode";
|
|
2831
|
+
ParseErrorCode2[ParseErrorCode2["InvalidEscapeCharacter"] = 15] = "InvalidEscapeCharacter";
|
|
2832
|
+
ParseErrorCode2[ParseErrorCode2["InvalidCharacter"] = 16] = "InvalidCharacter";
|
|
2833
|
+
})(ParseErrorCode || (ParseErrorCode = {}));
|
|
1698
2834
|
|
|
1699
|
-
// src/generators/shared/
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
if (tree.exists(`${prefix}tsconfig.lib.json`)) return "library";
|
|
1705
|
-
if (tree.exists(`${prefix}tsconfig.app.json`)) return "application";
|
|
1706
|
-
const raw = tree.read(`${prefix}package.json`, "utf-8");
|
|
1707
|
-
if (raw === null) return "library";
|
|
1708
|
-
try {
|
|
1709
|
-
const manifest = parseJson(raw);
|
|
1710
|
-
if (manifest && !["exports", "main", "module", "bin"].some((key) => Boolean(manifest[key]))) return "application";
|
|
1711
|
-
} catch {
|
|
2835
|
+
// src/generators/shared/json-edit.ts
|
|
2836
|
+
function detectIndent(raw) {
|
|
2837
|
+
for (const line of raw.split("\n")) {
|
|
2838
|
+
const match = /^(\t+| +)/.exec(line);
|
|
2839
|
+
if (match) return match[1];
|
|
1712
2840
|
}
|
|
1713
|
-
return "
|
|
2841
|
+
return " ";
|
|
1714
2842
|
}
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
var baseNameOf = (root) => root.split("/").pop() ?? root;
|
|
1718
|
-
function isEndToEnd(root) {
|
|
1719
|
-
const dir = baseNameOf(root);
|
|
1720
|
-
return dir === "e2e" || dir.endsWith("-e2e");
|
|
2843
|
+
function styleOf(raw) {
|
|
2844
|
+
return { eol: raw.includes("\r\n") ? "\r\n" : "\n", unit: detectIndent(raw) };
|
|
1721
2845
|
}
|
|
1722
|
-
function
|
|
1723
|
-
|
|
2846
|
+
function lineIndentAt(raw, offset) {
|
|
2847
|
+
const lineStart = raw.lastIndexOf("\n", offset - 1) + 1;
|
|
2848
|
+
return /^[ \t]*/.exec(raw.slice(lineStart))?.[0] ?? "";
|
|
1724
2849
|
}
|
|
1725
|
-
function
|
|
1726
|
-
|
|
1727
|
-
return config.root === "" || config.root === "." ? "src" : `${config.root}/src`;
|
|
2850
|
+
function isMultiline(raw, node) {
|
|
2851
|
+
return raw.slice(node.offset, node.offset + node.length).includes("\n");
|
|
1728
2852
|
}
|
|
1729
|
-
function
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
2853
|
+
function isPrimitive(value) {
|
|
2854
|
+
return value === null || ["string", "number", "boolean"].includes(typeof value);
|
|
2855
|
+
}
|
|
2856
|
+
function renderValue(value, indent, style) {
|
|
2857
|
+
if (isPrimitive(value)) return JSON.stringify(value);
|
|
2858
|
+
if (Array.isArray(value)) {
|
|
2859
|
+
if (value.every(isPrimitive)) return `[${value.map((item) => JSON.stringify(item)).join(", ")}]`;
|
|
2860
|
+
const inner2 = `${indent}${style.unit}`;
|
|
2861
|
+
return `[${style.eol}${value.map((item) => `${inner2}${renderValue(item, inner2, style)}`).join(`,${style.eol}`)}${style.eol}${indent}]`;
|
|
1737
2862
|
}
|
|
1738
|
-
const
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
2863
|
+
const entries = Object.entries(value);
|
|
2864
|
+
if (entries.length === 0) return "{}";
|
|
2865
|
+
const inner = `${indent}${style.unit}`;
|
|
2866
|
+
const body = entries.map(([key, item]) => `${inner}${JSON.stringify(key)}: ${renderValue(item, inner, style)}`).join(`,${style.eol}`);
|
|
2867
|
+
return `{${style.eol}${body}${style.eol}${indent}}`;
|
|
2868
|
+
}
|
|
2869
|
+
function splice(raw, start, end, text) {
|
|
2870
|
+
return raw.slice(0, start) + text + raw.slice(end);
|
|
2871
|
+
}
|
|
2872
|
+
function nest(keys, value) {
|
|
2873
|
+
return keys.reduceRight((inner, key) => ({ [key]: inner }), value);
|
|
2874
|
+
}
|
|
2875
|
+
function insertProperty(raw, object, key, value, style) {
|
|
2876
|
+
const properties = object.children ?? [];
|
|
2877
|
+
const objectIndent = lineIndentAt(raw, object.offset);
|
|
2878
|
+
if (properties.length === 0) {
|
|
2879
|
+
const inner = `${objectIndent}${style.unit}`;
|
|
2880
|
+
const text = `{${style.eol}${inner}${JSON.stringify(String(key))}: ${renderValue(value, inner, style)}${style.eol}${objectIndent}}`;
|
|
2881
|
+
return splice(raw, object.offset, object.offset + object.length, text);
|
|
1747
2882
|
}
|
|
1748
|
-
const
|
|
1749
|
-
const
|
|
1750
|
-
|
|
1751
|
-
(
|
|
1752
|
-
|
|
1753
|
-
const
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
2883
|
+
const last = properties[properties.length - 1];
|
|
2884
|
+
const end = last.offset + last.length;
|
|
2885
|
+
if (!isMultiline(raw, object)) {
|
|
2886
|
+
return splice(raw, end, end, `, ${JSON.stringify(String(key))}: ${renderValue(value, "", style)}`);
|
|
2887
|
+
}
|
|
2888
|
+
const indent = lineIndentAt(raw, last.offset);
|
|
2889
|
+
return splice(raw, end, end, `,${style.eol}${indent}${JSON.stringify(String(key))}: ${renderValue(value, indent, style)}`);
|
|
2890
|
+
}
|
|
2891
|
+
function setJsonValue(raw, path, value) {
|
|
2892
|
+
const style = styleOf(raw);
|
|
2893
|
+
const root = parseTree2(raw);
|
|
2894
|
+
if (root === void 0) throw new Error("setJsonValue: not a JSON document");
|
|
2895
|
+
const existing = findNodeAtLocation2(root, path);
|
|
2896
|
+
if (existing !== void 0) {
|
|
2897
|
+
return splice(raw, existing.offset, existing.offset + existing.length, renderValue(value, lineIndentAt(raw, existing.offset), style));
|
|
2898
|
+
}
|
|
2899
|
+
for (let depth = path.length - 1; depth >= 0; depth--) {
|
|
2900
|
+
const parent = depth === 0 ? root : findNodeAtLocation2(root, path.slice(0, depth));
|
|
2901
|
+
if (parent === void 0) continue;
|
|
2902
|
+
if (parent.type !== "object") throw new Error(`setJsonValue: ${path.slice(0, depth).join(".") || "the root"} is not an object`);
|
|
2903
|
+
return insertProperty(raw, parent, path[depth], nest(path.slice(depth + 1), value), style);
|
|
2904
|
+
}
|
|
2905
|
+
throw new Error("setJsonValue: unreachable");
|
|
2906
|
+
}
|
|
2907
|
+
function appendJsonArrayItem(raw, path, item) {
|
|
2908
|
+
const style = styleOf(raw);
|
|
2909
|
+
const root = parseTree2(raw);
|
|
2910
|
+
const array = root === void 0 ? void 0 : findNodeAtLocation2(root, path);
|
|
2911
|
+
if (array === void 0) return setJsonValue(raw, path, [item]);
|
|
2912
|
+
if (array.type !== "array") throw new Error(`appendJsonArrayItem: ${path.join(".")} is not an array`);
|
|
2913
|
+
const items = array.children ?? [];
|
|
2914
|
+
if (items.length === 0) return splice(raw, array.offset, array.offset + array.length, renderValue([item], "", style));
|
|
2915
|
+
const last = items[items.length - 1];
|
|
2916
|
+
const end = last.offset + last.length;
|
|
2917
|
+
if (!isMultiline(raw, array)) {
|
|
2918
|
+
const separator = items.length >= 2 ? raw.slice(items[0].offset + items[0].length, items[1].offset) : ", ";
|
|
2919
|
+
return splice(raw, end, end, `${separator}${renderValue(item, "", style)}`);
|
|
2920
|
+
}
|
|
2921
|
+
const indent = lineIndentAt(raw, last.offset);
|
|
2922
|
+
return splice(raw, end, end, `,${style.eol}${indent}${renderValue(item, indent, style)}`);
|
|
2923
|
+
}
|
|
2924
|
+
function applyJsonEdits(raw, edits) {
|
|
2925
|
+
if (raw === null) {
|
|
2926
|
+
let text = "{}\n";
|
|
2927
|
+
for (const [path, value] of edits) text = setJsonValue(text, path, value);
|
|
2928
|
+
return text;
|
|
2929
|
+
}
|
|
2930
|
+
return edits.reduce((text, [path, value]) => setJsonValue(text, path, value), raw);
|
|
1758
2931
|
}
|
|
1759
2932
|
|
|
1760
2933
|
// src/generators/shared/toolkit-config-file.ts
|
|
1761
2934
|
var TOOLKIT_CONFIG_PATH = "toolkit.config.json";
|
|
1762
|
-
function detectIndent(raw) {
|
|
1763
|
-
for (const line of raw.split("\n")) {
|
|
1764
|
-
const match = /^(\t+| +)/.exec(line);
|
|
1765
|
-
if (match) return match[1];
|
|
1766
|
-
}
|
|
1767
|
-
return " ";
|
|
1768
|
-
}
|
|
1769
2935
|
function readToolkitConfigFile(tree) {
|
|
1770
2936
|
const raw = tree.read(TOOLKIT_CONFIG_PATH, "utf-8");
|
|
1771
2937
|
if (raw === null) return { raw: null, parsed: null };
|
|
@@ -1791,18 +2957,18 @@ function recordedChoice(config, key, allowed) {
|
|
|
1791
2957
|
const value = config?.[key];
|
|
1792
2958
|
return typeof value === "string" && allowed.includes(value) ? value : void 0;
|
|
1793
2959
|
}
|
|
1794
|
-
var RECORDED_FRONTENDS = ["react-vite", "next", "none"];
|
|
2960
|
+
var RECORDED_FRONTENDS = ["react-vite", "next", "expo", "generic", "none"];
|
|
1795
2961
|
var RECORDED_BACKENDS = ["express", "nest", "none"];
|
|
1796
2962
|
var RECORDED_PRESETS = ["quick-start", "solo", "team"];
|
|
1797
2963
|
var RECORDED_PACKAGE_MANAGERS = ["pnpm", "npm", "yarn", "bun"];
|
|
1798
2964
|
function resolveFrontendOption(option, config, generator) {
|
|
1799
2965
|
const frontend = option ?? recordedChoice(config, "frontend", RECORDED_FRONTENDS);
|
|
1800
2966
|
if (frontend === void 0) {
|
|
1801
|
-
throw new Error(`${generator}: pass --frontend=react-vite|next (toolkit.config.json records none \u2014 init writes it).`);
|
|
2967
|
+
throw new Error(`${generator}: pass --frontend=react-vite|next|expo|generic (toolkit.config.json records none \u2014 init writes it).`);
|
|
1802
2968
|
}
|
|
1803
2969
|
if (frontend === "none") {
|
|
1804
2970
|
throw new Error(
|
|
1805
|
-
`${generator}: this is a backend-only workspace (toolkit.config.json frontend: none) \u2014 FSD structures a frontend app. Pass --frontend=react-vite|next if you added one.`
|
|
2971
|
+
`${generator}: this is a backend-only workspace (toolkit.config.json frontend: none) \u2014 FSD structures a frontend app. Pass --frontend=react-vite|next|expo|generic if you added one.`
|
|
1806
2972
|
);
|
|
1807
2973
|
}
|
|
1808
2974
|
return frontend;
|
|
@@ -1810,7 +2976,7 @@ function resolveFrontendOption(option, config, generator) {
|
|
|
1810
2976
|
function resolveInitFrontend(option, config) {
|
|
1811
2977
|
const frontend = option ?? recordedChoice(config, "frontend", RECORDED_FRONTENDS);
|
|
1812
2978
|
if (frontend === void 0) {
|
|
1813
|
-
throw new Error("init: pass --frontend=react-vite|next|none (toolkit.config.json records none \u2014 init writes it).");
|
|
2979
|
+
throw new Error("init: pass --frontend=react-vite|next|expo|generic|none (toolkit.config.json records none \u2014 init writes it).");
|
|
1814
2980
|
}
|
|
1815
2981
|
return frontend;
|
|
1816
2982
|
}
|
|
@@ -1826,21 +2992,123 @@ function resolveAiHostsOption(option, config) {
|
|
|
1826
2992
|
const fromConfig = Array.isArray(recorded) ? [...new Set(recorded.filter(isAiHost))] : [];
|
|
1827
2993
|
return fromConfig.length > 0 ? fromConfig : [...DEFAULT_AI_HOSTS];
|
|
1828
2994
|
}
|
|
1829
|
-
function recordAiHosts(tree, hosts, write) {
|
|
1830
|
-
const { raw, parsed } = readToolkitConfigFile(tree);
|
|
1831
|
-
if (raw !== null && parsed === null) return "unrecognized";
|
|
1832
|
-
const existing = parsed ?? {};
|
|
1833
|
-
const recorded = existing["aiHosts"];
|
|
1834
|
-
if (Array.isArray(recorded) && recorded.length === hosts.length && recorded.every((id, index) => id === hosts[index])) {
|
|
1835
|
-
return "unchanged";
|
|
2995
|
+
function recordAiHosts(tree, hosts, write) {
|
|
2996
|
+
const { raw, parsed } = readToolkitConfigFile(tree);
|
|
2997
|
+
if (raw !== null && parsed === null) return "unrecognized";
|
|
2998
|
+
const existing = parsed ?? {};
|
|
2999
|
+
const recorded = existing["aiHosts"];
|
|
3000
|
+
if (Array.isArray(recorded) && recorded.length === hosts.length && recorded.every((id, index) => id === hosts[index])) {
|
|
3001
|
+
return "unchanged";
|
|
3002
|
+
}
|
|
3003
|
+
if (write) tree.write(TOOLKIT_CONFIG_PATH, applyJsonEdits(raw, [[["aiHosts"], [...hosts]]]));
|
|
3004
|
+
return raw === null ? "created" : "updated";
|
|
3005
|
+
}
|
|
3006
|
+
function recordedFrontends(config) {
|
|
3007
|
+
const frontends = config?.["frontends"];
|
|
3008
|
+
if (!Array.isArray(frontends)) return [];
|
|
3009
|
+
return frontends.flatMap(
|
|
3010
|
+
(entry) => typeof entry === "object" && entry !== null && typeof entry["project"] === "string" ? [entry.project] : []
|
|
3011
|
+
);
|
|
3012
|
+
}
|
|
3013
|
+
|
|
3014
|
+
// src/generators/shared/project-type.ts
|
|
3015
|
+
import { parseJson } from "@nx/devkit";
|
|
3016
|
+
function resolveProjectType(tree, root, declared) {
|
|
3017
|
+
if (declared) return declared === "library" ? "library" : "application";
|
|
3018
|
+
const prefix = root === "" || root === "." ? "" : `${root}/`;
|
|
3019
|
+
if (tree.exists(`${prefix}tsconfig.lib.json`)) return "library";
|
|
3020
|
+
if (tree.exists(`${prefix}tsconfig.app.json`)) return "application";
|
|
3021
|
+
const raw = tree.read(`${prefix}package.json`, "utf-8");
|
|
3022
|
+
if (raw === null) return "library";
|
|
3023
|
+
try {
|
|
3024
|
+
const manifest = parseJson(raw);
|
|
3025
|
+
if (manifest && !["exports", "main", "module", "bin"].some((key) => Boolean(manifest[key]))) return "application";
|
|
3026
|
+
} catch {
|
|
3027
|
+
}
|
|
3028
|
+
return "library";
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
// src/generators/shared/frontend-app.ts
|
|
3032
|
+
var baseNameOf = (root) => root.split("/").pop() ?? root;
|
|
3033
|
+
function isEndToEnd(root) {
|
|
3034
|
+
const dir = baseNameOf(root);
|
|
3035
|
+
return dir === "e2e" || dir.endsWith("-e2e");
|
|
3036
|
+
}
|
|
3037
|
+
function isBackend(root, config, backendTag) {
|
|
3038
|
+
return baseNameOf(root) === "api" || (config.tags ?? []).includes(backendTag);
|
|
3039
|
+
}
|
|
3040
|
+
function prefixOf(root) {
|
|
3041
|
+
return root === "" || root === "." ? "" : `${root}/`;
|
|
3042
|
+
}
|
|
3043
|
+
function sourceRootOf(tree, config) {
|
|
3044
|
+
const prefix = prefixOf(config.root);
|
|
3045
|
+
const declared = config.sourceRoot?.replace(/\/$/, "");
|
|
3046
|
+
const atRoot = declared !== void 0 && (declared === config.root || prefix === "" && (declared === "." || declared === ""));
|
|
3047
|
+
if (declared && !atRoot) return declared;
|
|
3048
|
+
if (atRoot && !tree.exists(`${prefix}src`)) return prefix === "" ? "." : config.root;
|
|
3049
|
+
return `${prefix}src`;
|
|
3050
|
+
}
|
|
3051
|
+
function frameworkOf(tree, config) {
|
|
3052
|
+
return detectFramework({
|
|
3053
|
+
exists: (path) => tree.exists(path),
|
|
3054
|
+
read: (path) => tree.read(path, "utf-8"),
|
|
3055
|
+
projectRoot: config.root,
|
|
3056
|
+
executors: Object.values(config.targets ?? {}).flatMap((target) => target.executor ? [target.executor] : [])
|
|
3057
|
+
});
|
|
3058
|
+
}
|
|
3059
|
+
function resolveFrontendApp(tree, explicitProject) {
|
|
3060
|
+
let projects;
|
|
3061
|
+
try {
|
|
3062
|
+
projects = getProjects(tree);
|
|
3063
|
+
} catch (error) {
|
|
3064
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3065
|
+
const pathMatch = /Cannot parse ([^:]+):/.exec(message);
|
|
3066
|
+
return { kind: "invalid-project-json", detail: pathMatch ? pathMatch[1] : message };
|
|
1836
3067
|
}
|
|
1837
|
-
|
|
1838
|
-
const
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
3068
|
+
const found = (project2, config2) => {
|
|
3069
|
+
const profile = frameworkOf(tree, config2);
|
|
3070
|
+
return { kind: "found", project: project2, root: config2.root, sourceRoot: sourceRootOf(tree, config2), framework: profile.id, profile };
|
|
3071
|
+
};
|
|
3072
|
+
if (explicitProject !== void 0) {
|
|
3073
|
+
const config2 = projects.get(explicitProject);
|
|
3074
|
+
if (config2) return found(explicitProject, config2);
|
|
3075
|
+
const rootApp = inferredRootApp(tree, projects);
|
|
3076
|
+
return rootApp?.kind === "found" && rootApp.project === explicitProject ? rootApp : { kind: "not-found", project: explicitProject };
|
|
1842
3077
|
}
|
|
1843
|
-
|
|
3078
|
+
const backendTag = readBoundaryTags(readToolkitConfig(tree)).backend;
|
|
3079
|
+
const eligible = [...projects.entries()].filter(([, config2]) => !isEndToEnd(config2.root) && !isBackend(config2.root, config2, backendTag));
|
|
3080
|
+
const declared = eligible.filter(([, config2]) => config2.projectType === "application");
|
|
3081
|
+
const inferred = eligible.filter(([, config2]) => {
|
|
3082
|
+
if (config2.projectType !== void 0) return false;
|
|
3083
|
+
if (config2.root === "" || config2.root === ".") return frameworkOf(tree, config2).recognised;
|
|
3084
|
+
return resolveProjectType(tree, config2.root, void 0) === "application";
|
|
3085
|
+
});
|
|
3086
|
+
const candidates = (declared.length > 0 ? declared : inferred).sort(([a], [b]) => a.localeCompare(b));
|
|
3087
|
+
if (candidates.length === 0) {
|
|
3088
|
+
const rootApp = inferredRootApp(tree, projects);
|
|
3089
|
+
if (rootApp !== null) return rootApp;
|
|
3090
|
+
const otherProjects = [...projects.keys()].filter((name) => !isEndToEnd(projects.get(name)?.root ?? "")).sort();
|
|
3091
|
+
return { kind: "none", otherProjects };
|
|
3092
|
+
}
|
|
3093
|
+
if (candidates.length > 1) return { kind: "ambiguous", candidates: candidates.map(([name]) => name) };
|
|
3094
|
+
const [project, config] = candidates[0];
|
|
3095
|
+
return found(project, config);
|
|
3096
|
+
}
|
|
3097
|
+
function inferredRootApp(tree, projects) {
|
|
3098
|
+
if ([...projects.values()].some((config2) => config2.root === "" || config2.root === ".")) return null;
|
|
3099
|
+
const raw = tree.read("package.json", "utf-8");
|
|
3100
|
+
if (raw === null) return null;
|
|
3101
|
+
let name;
|
|
3102
|
+
try {
|
|
3103
|
+
name = JSON.parse(raw).name;
|
|
3104
|
+
} catch {
|
|
3105
|
+
return null;
|
|
3106
|
+
}
|
|
3107
|
+
if (typeof name !== "string" || name === "") return null;
|
|
3108
|
+
const config = { name, root: "." };
|
|
3109
|
+
const framework = frameworkOf(tree, config);
|
|
3110
|
+
if (!framework.recognised) return null;
|
|
3111
|
+
return { kind: "found", project: name, root: ".", sourceRoot: sourceRootOf(tree, config), framework: framework.id, profile: framework, inferredRoot: true };
|
|
1844
3112
|
}
|
|
1845
3113
|
|
|
1846
3114
|
// src/generators/add-fsd-lite/render.ts
|
|
@@ -1859,8 +3127,11 @@ function renderAddFsdLiteReport(report) {
|
|
|
1859
3127
|
for (const name of refusal.candidates) lines.push(` - ${name}`);
|
|
1860
3128
|
} else if (refusal.reason === "invalid-project-json") {
|
|
1861
3129
|
lines.push(` a project.json could not be parsed (${refusal.detail}) \u2014 fix it, then rerun.`);
|
|
3130
|
+
} else if (refusal.otherProjects.length > 0) {
|
|
3131
|
+
lines.push(" no project was recognised as a frontend app. If one of these is it, rerun with --project=<name>:");
|
|
3132
|
+
for (const name of refusal.otherProjects) lines.push(` - ${name}`);
|
|
1862
3133
|
} else {
|
|
1863
|
-
lines.push(" the workspace has no
|
|
3134
|
+
lines.push(" the workspace has no projects \u2014 create one (e.g. nx g @nx/react:app or nx g @nx/next:app), then rerun.");
|
|
1864
3135
|
}
|
|
1865
3136
|
}
|
|
1866
3137
|
return lines.join("\n");
|
|
@@ -1870,6 +3141,9 @@ function renderAddFsdLiteReport(report) {
|
|
|
1870
3141
|
lines.push("");
|
|
1871
3142
|
}
|
|
1872
3143
|
lines.push(`FSD layers (in ${report.project}, ${report.sourceRoot}):`);
|
|
3144
|
+
if (report.layerGuide !== null && report.frontend === "generic") {
|
|
3145
|
+
lines.push("Frontend: framework not recognised \u2014 the common FSD rules apply, with no framework-specific layer names.");
|
|
3146
|
+
}
|
|
1873
3147
|
if (report.layerGuide !== null) lines.push(report.layerGuide);
|
|
1874
3148
|
lines.push("");
|
|
1875
3149
|
const where = report.eslintPath ?? `${report.project}'s eslint.config.mjs`;
|
|
@@ -1921,26 +3195,31 @@ function runAddFsdLite(tree, options) {
|
|
|
1921
3195
|
const frontend = resolveFrontendOption(options.frontend, config, "add-fsd-lite");
|
|
1922
3196
|
const dryRun = options.dryRun ?? false;
|
|
1923
3197
|
const applyEslint = options.applyEslint ?? false;
|
|
1924
|
-
const snippet = buildFsdEslintPasteSnippet(enforcement, locale);
|
|
1925
3198
|
const app = resolveFrontendApp(tree, options.project);
|
|
1926
3199
|
if (app.kind !== "found") {
|
|
1927
|
-
const
|
|
1928
|
-
|
|
3200
|
+
const snippet2 = buildFsdEslintPasteSnippet(enforcement, locale);
|
|
3201
|
+
const appRefusal = app.kind === "not-found" ? { reason: "project-not-found", project: app.project } : app.kind === "ambiguous" ? { reason: "ambiguous-frontend", candidates: app.candidates } : app.kind === "invalid-project-json" ? { reason: "invalid-project-json", detail: app.detail } : { reason: "no-frontend-app", otherProjects: app.otherProjects };
|
|
3202
|
+
return { dryRun, aborted: true, appRefusal, project: null, sourceRoot: null, enforcement, frontend, eslint: "not-applied", eslintPath: null, snippet: snippet2, layerGuide: null, steiger: null };
|
|
1929
3203
|
}
|
|
1930
3204
|
const appDir = app.root === "" || app.root === "." ? "" : `${app.root}/`;
|
|
3205
|
+
const eslintRoot = posix.relative(app.root === "" ? "." : app.root, app.sourceRoot) || ".";
|
|
3206
|
+
const profile = app.framework === frontend ? app.profile : profileById(frontend);
|
|
3207
|
+
const { platformSuffixes, routerDirs } = profile;
|
|
3208
|
+
const framework = { platformSuffixes, routerDirs };
|
|
3209
|
+
const snippet = buildFsdEslintPasteSnippet(enforcement, locale, eslintRoot, framework);
|
|
1931
3210
|
const eslintPath = ESLINT_CONFIG_CANDIDATES.map((name) => `${appDir}${name}`).find((path) => tree.exists(path)) ?? null;
|
|
1932
3211
|
const configText = eslintPath === null ? null : tree.read(eslintPath, "utf-8");
|
|
1933
3212
|
let eslint;
|
|
1934
3213
|
if (!applyEslint) {
|
|
1935
|
-
eslint = configText !== null && applyFsdEslintConfig(configText, enforcement).status === "already-applied" ? "already-applied" : "snippet-only";
|
|
3214
|
+
eslint = configText !== null && applyFsdEslintConfig(configText, enforcement, eslintRoot, framework).status === "already-applied" ? "already-applied" : "snippet-only";
|
|
1936
3215
|
} else if (configText === null) {
|
|
1937
3216
|
eslint = "degraded-unrecognized";
|
|
1938
3217
|
} else {
|
|
1939
|
-
const result = applyFsdEslintConfig(configText, enforcement);
|
|
3218
|
+
const result = applyFsdEslintConfig(configText, enforcement, eslintRoot, framework);
|
|
1940
3219
|
eslint = result.status === "applied" ? "applied" : result.status === "already-applied" ? "already-applied" : "degraded-unrecognized";
|
|
1941
3220
|
if (result.status === "applied" && !dryRun && eslintPath !== null) tree.write(eslintPath, result.text);
|
|
1942
3221
|
}
|
|
1943
|
-
const steiger = options.steiger ? addSteiger(tree, { appDir, appRoot: app.root, sourceRoot: app.sourceRoot,
|
|
3222
|
+
const steiger = options.steiger ? addSteiger(tree, { appDir, appRoot: app.root, sourceRoot: app.sourceRoot, profile, locale, write: !dryRun, config }) : null;
|
|
1944
3223
|
return {
|
|
1945
3224
|
dryRun,
|
|
1946
3225
|
aborted: false,
|
|
@@ -1949,10 +3228,11 @@ function runAddFsdLite(tree, options) {
|
|
|
1949
3228
|
sourceRoot: app.sourceRoot,
|
|
1950
3229
|
steiger,
|
|
1951
3230
|
enforcement,
|
|
3231
|
+
frontend,
|
|
1952
3232
|
eslint,
|
|
1953
3233
|
eslintPath,
|
|
1954
3234
|
snippet: eslint === "applied" || eslint === "already-applied" ? null : snippet,
|
|
1955
|
-
layerGuide: composeFsdLayerGuide({
|
|
3235
|
+
layerGuide: composeFsdLayerGuide({ profile, sourceRoot: app.sourceRoot, locale })
|
|
1956
3236
|
};
|
|
1957
3237
|
}
|
|
1958
3238
|
function readJsonObject(tree, path) {
|
|
@@ -1967,7 +3247,7 @@ function readJsonObject(tree, path) {
|
|
|
1967
3247
|
}
|
|
1968
3248
|
function addSteiger(tree, input) {
|
|
1969
3249
|
const configPath = `${input.appDir}${STEIGER_CONFIG_FILE}`;
|
|
1970
|
-
const configText = buildSteigerConfig({
|
|
3250
|
+
const configText = buildSteigerConfig({ renamedLayerDirs: Object.values(input.profile.layerDirs), locale: input.locale });
|
|
1971
3251
|
const existing = tree.read(configPath, "utf-8");
|
|
1972
3252
|
let configAction;
|
|
1973
3253
|
if (existing === null) {
|
|
@@ -1994,8 +3274,8 @@ function addSteiger(tree, input) {
|
|
|
1994
3274
|
} else {
|
|
1995
3275
|
targets[FSD_CHECK_TARGET] = target;
|
|
1996
3276
|
targetAction = "added";
|
|
1997
|
-
|
|
1998
|
-
|
|
3277
|
+
const path = candidate.endsWith("project.json") ? ["targets", FSD_CHECK_TARGET] : ["nx", "targets", FSD_CHECK_TARGET];
|
|
3278
|
+
if (input.write) tree.write(candidate, setJsonValue(file.raw, path, target));
|
|
1999
3279
|
}
|
|
2000
3280
|
break;
|
|
2001
3281
|
}
|
|
@@ -2010,8 +3290,8 @@ function addSteiger(tree, input) {
|
|
|
2010
3290
|
} else {
|
|
2011
3291
|
for (const [name, range] of missing) devDependencies[name] = range;
|
|
2012
3292
|
dependenciesAction = "added";
|
|
2013
|
-
|
|
2014
|
-
|
|
3293
|
+
const edited = applyJsonEdits(rootPackage.raw, missing.map(([name, range]) => [["devDependencies", name], range]));
|
|
3294
|
+
if (input.write) tree.write("package.json", edited);
|
|
2015
3295
|
}
|
|
2016
3296
|
}
|
|
2017
3297
|
const pm = treePackageManager(tree, input.config) ?? "pnpm";
|
|
@@ -2033,7 +3313,8 @@ var ACTION_LINE = {
|
|
|
2033
3313
|
appended: (result) => `${result.filePath}: nx-archweave block added after the existing content (nothing else changed)`,
|
|
2034
3314
|
updated: (result) => `${result.filePath}: nx-archweave block updated (nothing outside it changed)`,
|
|
2035
3315
|
unchanged: (result) => `${result.filePath}: unchanged`,
|
|
2036
|
-
unrecognized: (result) => `${result.filePath}: not written \u2014 ${result.reason ?? "the nx-archweave markers could not be placed"}
|
|
3316
|
+
unrecognized: (result) => `${result.filePath}: not written \u2014 ${result.reason ?? "the nx-archweave markers could not be placed"}`,
|
|
3317
|
+
"user-modified": (result) => `${result.filePath}: not written \u2014 the nx-archweave block was edited by hand, so it is kept`
|
|
2037
3318
|
};
|
|
2038
3319
|
function renderAddAgentRulesReport(report) {
|
|
2039
3320
|
const lines = [];
|
|
@@ -2044,6 +3325,11 @@ function renderAddAgentRulesReport(report) {
|
|
|
2044
3325
|
lines.push("");
|
|
2045
3326
|
for (const file of report.files) {
|
|
2046
3327
|
lines.push(` ${ACTION_LINE[file.action](file)}`);
|
|
3328
|
+
if (file.action === "user-modified") {
|
|
3329
|
+
lines.push(" What this run would write instead (your block \u2192 generated):");
|
|
3330
|
+
for (const line of (file.diff ?? "").split("\n")) lines.push(` ${line}`);
|
|
3331
|
+
lines.push(" Rerun with --overwriteBlock to replace the block with the generated one.");
|
|
3332
|
+
}
|
|
2047
3333
|
}
|
|
2048
3334
|
const RECORD_LINE = {
|
|
2049
3335
|
created: "toolkit.config.json: aiHosts recorded (file created)",
|
|
@@ -2057,7 +3343,7 @@ function renderAddAgentRulesReport(report) {
|
|
|
2057
3343
|
if (recordLine !== null) lines.push(` ${recordLine}`);
|
|
2058
3344
|
if (report.files.some((file) => file.action === "unrecognized")) {
|
|
2059
3345
|
lines.push("");
|
|
2060
|
-
lines.push('Manual fix needed: keep exactly one "<!-- nx-archweave:agent-rules start -->" \u2026 "<!-- nx-archweave:agent-rules end -->"');
|
|
3346
|
+
lines.push('Manual fix needed: keep exactly one "<!-- nx-archweave:agent-rules start \u2026 -->" \u2026 "<!-- nx-archweave:agent-rules end -->"');
|
|
2061
3347
|
lines.push("pair in the file (or remove both markers), then rerun add-agent-rules.");
|
|
2062
3348
|
}
|
|
2063
3349
|
if (report.dryRun) {
|
|
@@ -2073,6 +3359,158 @@ function renderAddAgentRulesReport(report) {
|
|
|
2073
3359
|
return lines.join("\n");
|
|
2074
3360
|
}
|
|
2075
3361
|
|
|
3362
|
+
// src/generators/add-review/render.ts
|
|
3363
|
+
var DEPENDABOT_PATH = ".github/dependabot.yml";
|
|
3364
|
+
function renderLineDiff(existing, generated) {
|
|
3365
|
+
const a = existing.split("\n");
|
|
3366
|
+
const b = generated.split("\n");
|
|
3367
|
+
const m = a.length;
|
|
3368
|
+
const n = b.length;
|
|
3369
|
+
const lcs = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
3370
|
+
for (let i2 = m - 1; i2 >= 0; i2--) {
|
|
3371
|
+
for (let j2 = n - 1; j2 >= 0; j2--) {
|
|
3372
|
+
lcs[i2][j2] = a[i2] === b[j2] ? lcs[i2 + 1][j2 + 1] + 1 : Math.max(lcs[i2 + 1][j2], lcs[i2][j2 + 1]);
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
const out = ["--- existing", "+++ generated"];
|
|
3376
|
+
let i = 0;
|
|
3377
|
+
let j = 0;
|
|
3378
|
+
while (i < m && j < n) {
|
|
3379
|
+
if (a[i] === b[j]) {
|
|
3380
|
+
out.push(` ${a[i]}`);
|
|
3381
|
+
i++;
|
|
3382
|
+
j++;
|
|
3383
|
+
} else if (lcs[i + 1][j] >= lcs[i][j + 1]) {
|
|
3384
|
+
out.push(`- ${a[i]}`);
|
|
3385
|
+
i++;
|
|
3386
|
+
} else {
|
|
3387
|
+
out.push(`+ ${b[j]}`);
|
|
3388
|
+
j++;
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
while (i < m) out.push(`- ${a[i++]}`);
|
|
3392
|
+
while (j < n) out.push(`+ ${b[j++]}`);
|
|
3393
|
+
return out.join("\n");
|
|
3394
|
+
}
|
|
3395
|
+
var WORKFLOW_ACTION_LINES = {
|
|
3396
|
+
created: "created",
|
|
3397
|
+
overwritten: "overwritten with the generated version (per --onConflict=overwrite)",
|
|
3398
|
+
unchanged: "unchanged (already matches the generated version)",
|
|
3399
|
+
proposed: "NOT written \u2014 differs from the generated version (merge proposal below)",
|
|
3400
|
+
skipped: "left as-is (per --onConflict=skip)",
|
|
3401
|
+
alternate: "generated alongside the existing file",
|
|
3402
|
+
aborted: "NOT written"
|
|
3403
|
+
};
|
|
3404
|
+
var CONFIG_ACTION_LINES = {
|
|
3405
|
+
created: "created with the review field",
|
|
3406
|
+
updated: "review field updated",
|
|
3407
|
+
unchanged: "review field unchanged",
|
|
3408
|
+
"not-updated": "NOT updated \u2014 kept consistent with the workflow file that is actually in effect",
|
|
3409
|
+
aborted: "NOT updated"
|
|
3410
|
+
};
|
|
3411
|
+
function renderAddReviewReport(report) {
|
|
3412
|
+
const lines = [];
|
|
3413
|
+
if (report.packageManagerNote !== void 0) {
|
|
3414
|
+
lines.push(`Note: ${report.packageManagerNote}`);
|
|
3415
|
+
}
|
|
3416
|
+
if (report.workflowAction === "aborted") {
|
|
3417
|
+
lines.push("add-review aborted \u2014 nothing was written.");
|
|
3418
|
+
lines.push(` toolkit.config.json: ${report.configError ?? "unusable"}`);
|
|
3419
|
+
lines.push(" Fix the config file manually, then rerun the generator.");
|
|
3420
|
+
return lines.join("\n");
|
|
3421
|
+
}
|
|
3422
|
+
if (report.dryRun) {
|
|
3423
|
+
lines.push("add-review dry-run \u2014 no files were written.");
|
|
3424
|
+
lines.push("");
|
|
3425
|
+
lines.push("Generated workflow YAML:");
|
|
3426
|
+
lines.push(report.workflowYaml ?? "");
|
|
3427
|
+
lines.push("");
|
|
3428
|
+
lines.push("toolkit.config.json review field:");
|
|
3429
|
+
lines.push(report.configSnippetJson ?? "");
|
|
3430
|
+
lines.push("");
|
|
3431
|
+
lines.push("Effective decision:");
|
|
3432
|
+
}
|
|
3433
|
+
lines.push(` ${report.workflowPath}: ${WORKFLOW_ACTION_LINES[report.workflowAction]}`);
|
|
3434
|
+
lines.push(` toolkit.config.json: ${CONFIG_ACTION_LINES[report.configAction]}`);
|
|
3435
|
+
if (report.workflowAction === "proposed" && report.diff) {
|
|
3436
|
+
lines.push("");
|
|
3437
|
+
lines.push(report.diff);
|
|
3438
|
+
lines.push("");
|
|
3439
|
+
lines.push("To resolve, rerun with one of:");
|
|
3440
|
+
lines.push(" --onConflict=overwrite replace the existing file with the generated version");
|
|
3441
|
+
lines.push(" --onConflict=skip keep the existing file");
|
|
3442
|
+
lines.push(" --onConflict=alternate-filename write the generated version alongside it");
|
|
3443
|
+
}
|
|
3444
|
+
if (report.dependabot === "created") {
|
|
3445
|
+
lines.push(` ${DEPENDABOT_PATH}: created \u2014 Dependabot proposes newer pins for the workflow's actions`);
|
|
3446
|
+
} else if (report.dependabot === "present") {
|
|
3447
|
+
lines.push(` ${DEPENDABOT_PATH}: already updates github-actions`);
|
|
3448
|
+
} else if (report.dependabot === "proposed") {
|
|
3449
|
+
lines.push(` ${DEPENDABOT_PATH}: NOT changed \u2014 it has no github-actions entry. The workflow pins each action`);
|
|
3450
|
+
lines.push(" to a commit SHA; add this entry under `updates:` so those pins get update pull requests:");
|
|
3451
|
+
lines.push("");
|
|
3452
|
+
lines.push(report.dependabotProposal ?? "");
|
|
3453
|
+
}
|
|
3454
|
+
if (report.workflowAction === "alternate") {
|
|
3455
|
+
lines.push("");
|
|
3456
|
+
lines.push(" Review the generated file, then replace the main workflow file with it yourself;");
|
|
3457
|
+
lines.push(" the config stays pointing at the workflow that is actually in effect until you do.");
|
|
3458
|
+
}
|
|
3459
|
+
lines.push("");
|
|
3460
|
+
lines.push(`Note: the review workflow stays green-skipped until you register the ${report.requiredSecretName} secret.`);
|
|
3461
|
+
lines.push("Note: this workflow runs with elevated permissions only after it is merged to the default branch \u2014");
|
|
3462
|
+
lines.push("merging is the first real validation.");
|
|
3463
|
+
return lines.join("\n");
|
|
3464
|
+
}
|
|
3465
|
+
|
|
3466
|
+
// src/eslint/fsd-policy.ts
|
|
3467
|
+
var PRESETS = {
|
|
3468
|
+
basic: {
|
|
3469
|
+
sliceGroups: { auto: true, include: [], exclude: [] },
|
|
3470
|
+
sharedPublicApi: "segment",
|
|
3471
|
+
testFiles: false,
|
|
3472
|
+
segments: [],
|
|
3473
|
+
platformSuffixes: [],
|
|
3474
|
+
routerDirs: []
|
|
3475
|
+
},
|
|
3476
|
+
practical: {
|
|
3477
|
+
sliceGroups: { auto: true, include: [], exclude: [] },
|
|
3478
|
+
sharedPublicApi: "file",
|
|
3479
|
+
testFiles: ["**/*.{spec,test}.*", "**/__tests__/**"],
|
|
3480
|
+
segments: [],
|
|
3481
|
+
platformSuffixes: [],
|
|
3482
|
+
routerDirs: []
|
|
3483
|
+
}
|
|
3484
|
+
};
|
|
3485
|
+
var isStringArray = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
3486
|
+
function resolveFsdPolicy(recorded, overrides = {}) {
|
|
3487
|
+
const fsd = typeof recorded === "object" && recorded !== null ? recorded : {};
|
|
3488
|
+
const preset = fsd["preset"] === "practical" ? "practical" : "basic";
|
|
3489
|
+
const policy = { ...PRESETS[preset] };
|
|
3490
|
+
const groups = fsd["sliceGroups"];
|
|
3491
|
+
if (groups === false) policy.sliceGroups = false;
|
|
3492
|
+
else if (groups === "auto") policy.sliceGroups = { auto: true, include: [], exclude: [] };
|
|
3493
|
+
else if (typeof groups === "object" && groups !== null) {
|
|
3494
|
+
const value = groups;
|
|
3495
|
+
policy.sliceGroups = {
|
|
3496
|
+
auto: value["auto"] !== false,
|
|
3497
|
+
include: isStringArray(value["include"]) ? value["include"] : [],
|
|
3498
|
+
exclude: isStringArray(value["exclude"]) ? value["exclude"] : []
|
|
3499
|
+
};
|
|
3500
|
+
}
|
|
3501
|
+
const shared = fsd["sharedPublicApi"];
|
|
3502
|
+
if (shared === "segment" || shared === "component" || shared === "file") policy.sharedPublicApi = shared;
|
|
3503
|
+
const tests = fsd["testFiles"];
|
|
3504
|
+
if (tests === false || isStringArray(tests)) policy.testFiles = tests;
|
|
3505
|
+
if (isStringArray(fsd["segments"])) policy.segments = fsd["segments"];
|
|
3506
|
+
if (isStringArray(fsd["platformSuffixes"])) policy.platformSuffixes = fsd["platformSuffixes"];
|
|
3507
|
+
if (isStringArray(fsd["routerDirs"])) policy.routerDirs = fsd["routerDirs"];
|
|
3508
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
3509
|
+
if (value !== void 0) policy[key] = value;
|
|
3510
|
+
}
|
|
3511
|
+
return policy;
|
|
3512
|
+
}
|
|
3513
|
+
|
|
2076
3514
|
// src/generators/add-agent-rules/generator.ts
|
|
2077
3515
|
function resolveTeamRules(option, config) {
|
|
2078
3516
|
if (option !== void 0) return option;
|
|
@@ -2087,6 +3525,13 @@ function resolveFsdStructure(config) {
|
|
|
2087
3525
|
const frontend = config?.["frontend"];
|
|
2088
3526
|
return typeof frontend === "string" && frontend !== "none";
|
|
2089
3527
|
}
|
|
3528
|
+
var POLICY_KEYS = ["preset", "sliceGroups", "sharedPublicApi", "testFiles"];
|
|
3529
|
+
function resolveFsdPolicyNotes(config) {
|
|
3530
|
+
const fsd = config?.["fsd"];
|
|
3531
|
+
if (typeof fsd !== "object" || fsd === null || !POLICY_KEYS.some((key) => key in fsd)) return void 0;
|
|
3532
|
+
const policy = resolveFsdPolicy(fsd);
|
|
3533
|
+
return { sliceGroups: policy.sliceGroups !== false, sharedPublicApi: policy.sharedPublicApi, testFiles: policy.testFiles !== false };
|
|
3534
|
+
}
|
|
2090
3535
|
function runAddAgentRules(tree, options, { recordHosts = true, fsdStructure } = {}) {
|
|
2091
3536
|
const config = readToolkitConfig(tree);
|
|
2092
3537
|
const hosts = resolveAiHostsOption(options.hosts, config);
|
|
@@ -2094,7 +3539,7 @@ function runAddAgentRules(tree, options, { recordHosts = true, fsdStructure } =
|
|
|
2094
3539
|
const locale = resolveLocaleOption(options.locale, config);
|
|
2095
3540
|
const teamRules = resolveTeamRules(options.teamRules, config);
|
|
2096
3541
|
const dryRun = options.dryRun ?? false;
|
|
2097
|
-
const composed = composeAgentRules({ files, locale, teamRules, fsdStructure: fsdStructure ?? resolveFsdStructure(config) });
|
|
3542
|
+
const composed = composeAgentRules({ files, locale, teamRules, fsdStructure: fsdStructure ?? resolveFsdStructure(config), fsdPolicy: resolveFsdPolicyNotes(config) });
|
|
2098
3543
|
const results = composed.map(({ filePath, content }) => {
|
|
2099
3544
|
if (!validateAgentRules(content, teamRules ? ["team-rules"] : [])) {
|
|
2100
3545
|
throw new Error(
|
|
@@ -2102,12 +3547,20 @@ function runAddAgentRules(tree, options, { recordHosts = true, fsdStructure } =
|
|
|
2102
3547
|
);
|
|
2103
3548
|
}
|
|
2104
3549
|
const existing = tree.read(filePath, "utf-8");
|
|
2105
|
-
const outcome = applyAgentRulesBlock(existing, content);
|
|
3550
|
+
const outcome = applyAgentRulesBlock(existing, content, { overwrite: options.overwriteBlock === true });
|
|
2106
3551
|
switch (outcome.kind) {
|
|
2107
3552
|
case "unchanged":
|
|
2108
3553
|
return { filePath, action: "unchanged", reason: null, content: null };
|
|
2109
3554
|
case "unrecognized":
|
|
2110
3555
|
return { filePath, action: "unrecognized", reason: outcome.reason, content: null };
|
|
3556
|
+
case "user-modified":
|
|
3557
|
+
return {
|
|
3558
|
+
filePath,
|
|
3559
|
+
action: "user-modified",
|
|
3560
|
+
reason: null,
|
|
3561
|
+
content: null,
|
|
3562
|
+
diff: renderLineDiff(outcome.existingBody, outcome.generatedBody)
|
|
3563
|
+
};
|
|
2111
3564
|
case "create":
|
|
2112
3565
|
case "append":
|
|
2113
3566
|
case "replace": {
|
|
@@ -2203,99 +3656,6 @@ function runAddGithubTemplates(tree, options) {
|
|
|
2203
3656
|
return { dryRun, files };
|
|
2204
3657
|
}
|
|
2205
3658
|
|
|
2206
|
-
// src/generators/add-review/render.ts
|
|
2207
|
-
function renderLineDiff(existing, generated) {
|
|
2208
|
-
const a = existing.split("\n");
|
|
2209
|
-
const b = generated.split("\n");
|
|
2210
|
-
const m = a.length;
|
|
2211
|
-
const n = b.length;
|
|
2212
|
-
const lcs = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
2213
|
-
for (let i2 = m - 1; i2 >= 0; i2--) {
|
|
2214
|
-
for (let j2 = n - 1; j2 >= 0; j2--) {
|
|
2215
|
-
lcs[i2][j2] = a[i2] === b[j2] ? lcs[i2 + 1][j2 + 1] + 1 : Math.max(lcs[i2 + 1][j2], lcs[i2][j2 + 1]);
|
|
2216
|
-
}
|
|
2217
|
-
}
|
|
2218
|
-
const out = ["--- existing", "+++ generated"];
|
|
2219
|
-
let i = 0;
|
|
2220
|
-
let j = 0;
|
|
2221
|
-
while (i < m && j < n) {
|
|
2222
|
-
if (a[i] === b[j]) {
|
|
2223
|
-
out.push(` ${a[i]}`);
|
|
2224
|
-
i++;
|
|
2225
|
-
j++;
|
|
2226
|
-
} else if (lcs[i + 1][j] >= lcs[i][j + 1]) {
|
|
2227
|
-
out.push(`- ${a[i]}`);
|
|
2228
|
-
i++;
|
|
2229
|
-
} else {
|
|
2230
|
-
out.push(`+ ${b[j]}`);
|
|
2231
|
-
j++;
|
|
2232
|
-
}
|
|
2233
|
-
}
|
|
2234
|
-
while (i < m) out.push(`- ${a[i++]}`);
|
|
2235
|
-
while (j < n) out.push(`+ ${b[j++]}`);
|
|
2236
|
-
return out.join("\n");
|
|
2237
|
-
}
|
|
2238
|
-
var WORKFLOW_ACTION_LINES = {
|
|
2239
|
-
created: "created",
|
|
2240
|
-
overwritten: "overwritten with the generated version (per --onConflict=overwrite)",
|
|
2241
|
-
unchanged: "unchanged (already matches the generated version)",
|
|
2242
|
-
proposed: "NOT written \u2014 differs from the generated version (merge proposal below)",
|
|
2243
|
-
skipped: "left as-is (per --onConflict=skip)",
|
|
2244
|
-
alternate: "generated alongside the existing file",
|
|
2245
|
-
aborted: "NOT written"
|
|
2246
|
-
};
|
|
2247
|
-
var CONFIG_ACTION_LINES = {
|
|
2248
|
-
created: "created with the review field",
|
|
2249
|
-
updated: "review field updated",
|
|
2250
|
-
unchanged: "review field unchanged",
|
|
2251
|
-
"not-updated": "NOT updated \u2014 kept consistent with the workflow file that is actually in effect",
|
|
2252
|
-
aborted: "NOT updated"
|
|
2253
|
-
};
|
|
2254
|
-
function renderAddReviewReport(report) {
|
|
2255
|
-
const lines = [];
|
|
2256
|
-
if (report.packageManagerNote !== void 0) {
|
|
2257
|
-
lines.push(`Note: ${report.packageManagerNote}`);
|
|
2258
|
-
}
|
|
2259
|
-
if (report.workflowAction === "aborted") {
|
|
2260
|
-
lines.push("add-review aborted \u2014 nothing was written.");
|
|
2261
|
-
lines.push(` toolkit.config.json: ${report.configError ?? "unusable"}`);
|
|
2262
|
-
lines.push(" Fix the config file manually, then rerun the generator.");
|
|
2263
|
-
return lines.join("\n");
|
|
2264
|
-
}
|
|
2265
|
-
if (report.dryRun) {
|
|
2266
|
-
lines.push("add-review dry-run \u2014 no files were written.");
|
|
2267
|
-
lines.push("");
|
|
2268
|
-
lines.push("Generated workflow YAML:");
|
|
2269
|
-
lines.push(report.workflowYaml ?? "");
|
|
2270
|
-
lines.push("");
|
|
2271
|
-
lines.push("toolkit.config.json review field:");
|
|
2272
|
-
lines.push(report.configSnippetJson ?? "");
|
|
2273
|
-
lines.push("");
|
|
2274
|
-
lines.push("Effective decision:");
|
|
2275
|
-
}
|
|
2276
|
-
lines.push(` ${report.workflowPath}: ${WORKFLOW_ACTION_LINES[report.workflowAction]}`);
|
|
2277
|
-
lines.push(` toolkit.config.json: ${CONFIG_ACTION_LINES[report.configAction]}`);
|
|
2278
|
-
if (report.workflowAction === "proposed" && report.diff) {
|
|
2279
|
-
lines.push("");
|
|
2280
|
-
lines.push(report.diff);
|
|
2281
|
-
lines.push("");
|
|
2282
|
-
lines.push("To resolve, rerun with one of:");
|
|
2283
|
-
lines.push(" --onConflict=overwrite replace the existing file with the generated version");
|
|
2284
|
-
lines.push(" --onConflict=skip keep the existing file");
|
|
2285
|
-
lines.push(" --onConflict=alternate-filename write the generated version alongside it");
|
|
2286
|
-
}
|
|
2287
|
-
if (report.workflowAction === "alternate") {
|
|
2288
|
-
lines.push("");
|
|
2289
|
-
lines.push(" Review the generated file, then replace the main workflow file with it yourself;");
|
|
2290
|
-
lines.push(" the config stays pointing at the workflow that is actually in effect until you do.");
|
|
2291
|
-
}
|
|
2292
|
-
lines.push("");
|
|
2293
|
-
lines.push(`Note: the review workflow stays green-skipped until you register the ${report.requiredSecretName} secret.`);
|
|
2294
|
-
lines.push("Note: this workflow runs with elevated permissions only after it is merged to the default branch \u2014");
|
|
2295
|
-
lines.push("merging is the first real validation.");
|
|
2296
|
-
return lines.join("\n");
|
|
2297
|
-
}
|
|
2298
|
-
|
|
2299
3659
|
// src/generators/add-review/generator.ts
|
|
2300
3660
|
var CONFIG_PATH = "toolkit.config.json";
|
|
2301
3661
|
function deriveAltWorkflowPath(mainWorkflowPath) {
|
|
@@ -2369,7 +3729,9 @@ function runAddReview(tree, options) {
|
|
|
2369
3729
|
diff: null,
|
|
2370
3730
|
workflowYaml: null,
|
|
2371
3731
|
configSnippetJson: null,
|
|
2372
|
-
configError: config.error
|
|
3732
|
+
configError: config.error,
|
|
3733
|
+
dependabot: "not-applied",
|
|
3734
|
+
dependabotProposal: null
|
|
2373
3735
|
};
|
|
2374
3736
|
}
|
|
2375
3737
|
const existing = tree.read(plan.workflowFilePath, "utf-8");
|
|
@@ -2421,17 +3783,17 @@ function runAddReview(tree, options) {
|
|
|
2421
3783
|
configAction = "unchanged";
|
|
2422
3784
|
} else {
|
|
2423
3785
|
configAction = config.raw === null ? "created" : "updated";
|
|
2424
|
-
|
|
2425
|
-
const trailingNewline = config.raw === null || config.raw.endsWith("\n");
|
|
2426
|
-
const body = JSON.stringify({ ...config.parsed ?? {}, review: plan.configSnippet }, null, indent);
|
|
2427
|
-
newConfigContent = trailingNewline ? `${body}
|
|
2428
|
-
` : body;
|
|
3786
|
+
newConfigContent = applyJsonEdits(config.raw, [[["review"], plan.configSnippet]]);
|
|
2429
3787
|
}
|
|
2430
3788
|
}
|
|
3789
|
+
const [dependabotFile] = renderTemplateFiles(generatorFilesDir("add-review", import.meta.url), ".github", {});
|
|
3790
|
+
const existingDependabot = tree.read(DEPENDABOT_PATH, "utf-8");
|
|
3791
|
+
const dependabot = existingDependabot === null ? "created" : /github-actions/.test(existingDependabot) ? "present" : "proposed";
|
|
2431
3792
|
const dryRun = options.dryRun ?? false;
|
|
2432
3793
|
if (!dryRun) {
|
|
2433
3794
|
if (writeWorkflowTo !== null) tree.write(writeWorkflowTo, plan.workflowYaml);
|
|
2434
3795
|
if (newConfigContent !== null) tree.write(CONFIG_PATH, newConfigContent);
|
|
3796
|
+
if (dependabot === "created") tree.write(DEPENDABOT_PATH, dependabotFile.content);
|
|
2435
3797
|
}
|
|
2436
3798
|
return {
|
|
2437
3799
|
dryRun,
|
|
@@ -2443,6 +3805,8 @@ function runAddReview(tree, options) {
|
|
|
2443
3805
|
workflowYaml: dryRun ? plan.workflowYaml : null,
|
|
2444
3806
|
configSnippetJson: dryRun ? JSON.stringify(plan.configSnippet, null, 2) : null,
|
|
2445
3807
|
configError: null,
|
|
3808
|
+
dependabot,
|
|
3809
|
+
dependabotProposal: dependabot === "proposed" ? dependabotFile.content : null,
|
|
2446
3810
|
...packageManagerNote !== void 0 ? { packageManagerNote } : {}
|
|
2447
3811
|
};
|
|
2448
3812
|
}
|
|
@@ -2492,8 +3856,9 @@ function renderAddBackendBody(report) {
|
|
|
2492
3856
|
}
|
|
2493
3857
|
if (report.packageManagerNote !== void 0) lines.push(` note: ${report.packageManagerNote}`);
|
|
2494
3858
|
lines.push(` frontend project: ${report.frontendProject ?? "none (backend-only workspace)"}`);
|
|
3859
|
+
const libPath = report.sharedTypesRoot ?? "libs/shared-types";
|
|
2495
3860
|
lines.push(
|
|
2496
|
-
report.sharedTypes === "created" ? `
|
|
3861
|
+
report.sharedTypes === "created" ? ` ${libPath}: created (import it as ${report.sharedTypesPackage})` : report.sharedTypes === "disabled" ? " shared-types lib: none (sharedTypes: false in toolkit.config.json)" : ` ${libPath}: already exists \u2014 skipped`
|
|
2497
3862
|
);
|
|
2498
3863
|
if (report.config !== void 0 && report.config !== "unchanged") {
|
|
2499
3864
|
lines.push(` toolkit.config.json: backend "${report.backend}" recorded (${report.config}) \u2014 doctor's backend checks now run`);
|
|
@@ -2503,11 +3868,11 @@ function renderAddBackendBody(report) {
|
|
|
2503
3868
|
const registry = packageManagerCommands(pmName).workspaceRegistration;
|
|
2504
3869
|
lines.push(
|
|
2505
3870
|
{
|
|
2506
|
-
added: ` ${registry}: '
|
|
2507
|
-
present: ` ${registry}: already covers
|
|
3871
|
+
added: ` ${registry}: '${libPath}' registered \u2014 run \`${pmName} install\` once to link it`,
|
|
3872
|
+
present: ` ${registry}: already covers ${libPath}`,
|
|
2508
3873
|
unrecognized: (
|
|
2509
3874
|
// an integrated layout lists no workspace packages: the paths alias below links it
|
|
2510
|
-
report.tsconfigPaths === "added" || report.tsconfigPaths === "present" ? ` ${registry}: no workspace package list (integrated layout) \u2014 the tsconfig paths alias links
|
|
3875
|
+
report.tsconfigPaths === "added" || report.tsconfigPaths === "present" ? ` ${registry}: no workspace package list (integrated layout) \u2014 the tsconfig paths alias links ${libPath}` : ` ${registry}: shape not recognized \u2014 add '${libPath}' to its workspace list yourself`
|
|
2511
3876
|
)
|
|
2512
3877
|
}[report.workspaceGlob]
|
|
2513
3878
|
);
|
|
@@ -2520,7 +3885,8 @@ function renderAddBackendBody(report) {
|
|
|
2520
3885
|
added: ` tsconfig.base.json paths: ${report.sharedTypesPackage} added (a classic or integrated setup \u2014 libs resolve through paths)`,
|
|
2521
3886
|
present: " tsconfig.base.json paths: already wired",
|
|
2522
3887
|
"workspace-link": " tsconfig paths: not needed \u2014 the workspace package link is the wiring",
|
|
2523
|
-
unrecognized: " tsconfig.base.json: could not be parsed \u2014 left untouched"
|
|
3888
|
+
unrecognized: " tsconfig.base.json: could not be parsed \u2014 left untouched",
|
|
3889
|
+
none: " tsconfig paths: nothing to wire (no shared-types lib)"
|
|
2524
3890
|
}[report.tsconfigPaths]
|
|
2525
3891
|
);
|
|
2526
3892
|
switch (report.eslint) {
|
|
@@ -2547,7 +3913,6 @@ function renderAddBackendBody(report) {
|
|
|
2547
3913
|
}
|
|
2548
3914
|
|
|
2549
3915
|
// src/generators/add-backend/generator.ts
|
|
2550
|
-
var SHARED_TYPES_ROOT = "libs/shared-types";
|
|
2551
3916
|
function runAddBackend(tree, options) {
|
|
2552
3917
|
const backend = options.backend;
|
|
2553
3918
|
const apiName = options.apiName ?? "api";
|
|
@@ -2622,8 +3987,9 @@ function runAddBackend(tree, options) {
|
|
|
2622
3987
|
} catch {
|
|
2623
3988
|
}
|
|
2624
3989
|
}
|
|
2625
|
-
const
|
|
2626
|
-
const
|
|
3990
|
+
const frontendRoot = projects.get(frontendProject)?.root ?? "";
|
|
3991
|
+
const frontendParent = frontendRoot.split("/").slice(0, -1).join("/");
|
|
3992
|
+
const apiDirectory = frontendRoot === "" || frontendRoot === "." ? `apps/${apiName}` : `${frontendParent === "" ? "" : `${frontendParent}/`}${apiName}`;
|
|
2627
3993
|
const expressAlignment = backend === "express" && !expressDeclared ? readExpressPeerRange(tree) : void 0;
|
|
2628
3994
|
const quotedExpress = expressAlignment ? `"express@${expressAlignment}"` : void 0;
|
|
2629
3995
|
return {
|
|
@@ -2639,16 +4005,21 @@ function runAddBackend(tree, options) {
|
|
|
2639
4005
|
...refusalShape
|
|
2640
4006
|
};
|
|
2641
4007
|
}
|
|
2642
|
-
const
|
|
4008
|
+
const recordedConfig = readToolkitConfigFile(tree).parsed;
|
|
4009
|
+
const boundaryTags = readBoundaryTags(recordedConfig);
|
|
4010
|
+
const sharedTypesConfig = readSharedTypesConfig(recordedConfig);
|
|
4011
|
+
const withSharedTypes = sharedTypesConfig !== false;
|
|
4012
|
+
const sharedTypesRoot = sharedTypesConfig === false ? "" : sharedTypesConfig.path;
|
|
4013
|
+
const sharedTypesExists = withSharedTypes && (tree.exists(`${sharedTypesRoot}/package.json`) || tree.exists(`${sharedTypesRoot}/project.json`));
|
|
2643
4014
|
const rootPackageJson = tree.read("package.json", "utf-8");
|
|
2644
4015
|
const workspaceName = rootPackageJson ? JSON.parse(rootPackageJson).name ?? "workspace" : "workspace";
|
|
2645
|
-
const wiring = composeBackendWiring({ workspaceName });
|
|
4016
|
+
const wiring = composeBackendWiring({ workspaceName, tags: boundaryTags, ...sharedTypesConfig === false ? {} : { sharedTypes: sharedTypesConfig } });
|
|
2646
4017
|
const [apiProjectName, apiConfig] = apiEntry;
|
|
2647
4018
|
const tagTargets = [
|
|
2648
4019
|
...frontendProject === void 0 ? [] : [{ project: frontendProject, root: projects.get(frontendProject)?.root ?? `apps/${frontendProject}` }],
|
|
2649
4020
|
{ project: apiProjectName, root: apiConfig.root }
|
|
2650
4021
|
];
|
|
2651
|
-
if (sharedTypesExists) tagTargets.push({ project: wiring.sharedTypesPackageName, root:
|
|
4022
|
+
if (sharedTypesExists) tagTargets.push({ project: wiring.sharedTypesPackageName, root: sharedTypesRoot });
|
|
2652
4023
|
const parsedTargets = /* @__PURE__ */ new Map();
|
|
2653
4024
|
const invalidProjects = [];
|
|
2654
4025
|
for (const target of tagTargets) {
|
|
@@ -2670,7 +4041,7 @@ function runAddBackend(tree, options) {
|
|
|
2670
4041
|
if (invalidProjects.length > 0) {
|
|
2671
4042
|
return { ...base, refused: "invalid-project-json", frontendProject, invalidProjects, ...refusalShape };
|
|
2672
4043
|
}
|
|
2673
|
-
const sharedTypes = sharedTypesExists ? "skipped" : "created";
|
|
4044
|
+
const sharedTypes = !withSharedTypes ? "disabled" : sharedTypesExists ? "skipped" : "created";
|
|
2674
4045
|
if (sharedTypes === "created" && write) {
|
|
2675
4046
|
for (const file of wiring.sharedTypesFiles) tree.write(file.filePath, file.content);
|
|
2676
4047
|
}
|
|
@@ -2681,20 +4052,19 @@ function runAddBackend(tree, options) {
|
|
|
2681
4052
|
const existing = configParsed ?? {};
|
|
2682
4053
|
if (parseOk && existing["backend"] !== backend) {
|
|
2683
4054
|
configOutcome = configRaw === null ? "created" : "updated";
|
|
2684
|
-
|
|
2685
|
-
if (write) tree.write(TOOLKIT_CONFIG_PATH, `${JSON.stringify({ ...existing, backend }, null, indent)}
|
|
2686
|
-
`);
|
|
4055
|
+
if (write) tree.write(TOOLKIT_CONFIG_PATH, applyJsonEdits(configRaw, [[["backend"], backend]]));
|
|
2687
4056
|
}
|
|
2688
4057
|
}
|
|
2689
|
-
let workspaceGlob = "unrecognized";
|
|
2690
|
-
const workspaceYaml = tree.read("pnpm-workspace.yaml", "utf-8");
|
|
2691
|
-
if (
|
|
2692
|
-
|
|
4058
|
+
let workspaceGlob = withSharedTypes ? "unrecognized" : void 0;
|
|
4059
|
+
const workspaceYaml = withSharedTypes ? tree.read("pnpm-workspace.yaml", "utf-8") : null;
|
|
4060
|
+
if (!withSharedTypes) {
|
|
4061
|
+
} else if (workspaceYaml !== null) {
|
|
4062
|
+
const globResult = addSharedTypesWorkspaceGlob(workspaceYaml, sharedTypesRoot);
|
|
2693
4063
|
workspaceGlob = globResult.status;
|
|
2694
4064
|
if (globResult.status === "added" && write) tree.write("pnpm-workspace.yaml", globResult.text);
|
|
2695
4065
|
} else if ((detectedPm === "npm" || detectedPm === "yarn" || detectedPm === "bun") && rootPackageJson !== null) {
|
|
2696
4066
|
const yarnNeedsPrivate = detectedPm === "yarn" && JSON.parse(rootPackageJson).private !== true;
|
|
2697
|
-
const npmResult = yarnNeedsPrivate ? { status: "unrecognized" } : addSharedTypesNpmWorkspace(rootPackageJson);
|
|
4067
|
+
const npmResult = yarnNeedsPrivate ? { status: "unrecognized" } : addSharedTypesNpmWorkspace(rootPackageJson, sharedTypesRoot);
|
|
2698
4068
|
workspaceGlob = npmResult.status;
|
|
2699
4069
|
if (npmResult.status === "added" && write) tree.write("package.json", npmResult.text);
|
|
2700
4070
|
}
|
|
@@ -2705,17 +4075,17 @@ function runAddBackend(tree, options) {
|
|
|
2705
4075
|
const tags2 = holder.tags ?? [];
|
|
2706
4076
|
if (tags2.includes(tag)) return { project, action: "present" };
|
|
2707
4077
|
holder.tags = [...tags2, tag];
|
|
2708
|
-
|
|
2709
|
-
|
|
4078
|
+
target.raw = appendJsonArrayItem(target.raw, target.kind === "project" ? ["tags"] : ["nx", "tags"], tag);
|
|
4079
|
+
if (write) tree.write(target.path, target.raw);
|
|
2710
4080
|
return { project, action: "added" };
|
|
2711
4081
|
};
|
|
2712
4082
|
const tags = [
|
|
2713
4083
|
...frontendProject === void 0 ? [] : [mergeTag(frontendProject, wiring.tagByRole.frontend)],
|
|
2714
4084
|
mergeTag(apiProjectName, wiring.tagByRole.backend),
|
|
2715
|
-
mergeTag(wiring.sharedTypesPackageName, wiring.tagByRole.shared)
|
|
4085
|
+
...withSharedTypes ? [mergeTag(wiring.sharedTypesPackageName, wiring.tagByRole.shared)] : []
|
|
2716
4086
|
];
|
|
2717
|
-
let tsconfigPaths = "workspace-link";
|
|
2718
|
-
const tsconfigRaw = tree.read("tsconfig.base.json", "utf-8");
|
|
4087
|
+
let tsconfigPaths = withSharedTypes ? "workspace-link" : "none";
|
|
4088
|
+
const tsconfigRaw = withSharedTypes ? tree.read("tsconfig.base.json", "utf-8") : null;
|
|
2719
4089
|
if (tsconfigRaw !== null) {
|
|
2720
4090
|
try {
|
|
2721
4091
|
const tsconfig = JSON.parse(tsconfigRaw);
|
|
@@ -2726,10 +4096,8 @@ function runAddBackend(tree, options) {
|
|
|
2726
4096
|
tsconfigPaths = "present";
|
|
2727
4097
|
} else {
|
|
2728
4098
|
tsconfigPaths = "added";
|
|
2729
|
-
|
|
2730
|
-
tsconfig.compilerOptions
|
|
2731
|
-
if (write) tree.write("tsconfig.base.json", `${JSON.stringify(tsconfig, null, detectIndent(tsconfigRaw))}
|
|
2732
|
-
`);
|
|
4099
|
+
const entry = [`${baseUrl ? "" : "./"}${sharedTypesRoot}/src/index.ts`];
|
|
4100
|
+
if (write) tree.write("tsconfig.base.json", setJsonValue(tsconfigRaw, ["compilerOptions", "paths", wiring.sharedTypesPackageName], entry));
|
|
2733
4101
|
}
|
|
2734
4102
|
}
|
|
2735
4103
|
} catch {
|
|
@@ -2740,11 +4108,11 @@ function runAddBackend(tree, options) {
|
|
|
2740
4108
|
let snippet;
|
|
2741
4109
|
if (!applyEslint) {
|
|
2742
4110
|
eslint = "snippet-only";
|
|
2743
|
-
snippet = buildBoundarySnippet();
|
|
4111
|
+
snippet = buildBoundarySnippet(boundaryTags);
|
|
2744
4112
|
} else {
|
|
2745
4113
|
const configPath = ESLINT_CONFIG_CANDIDATES.find((candidate) => tree.exists(candidate));
|
|
2746
4114
|
const configText = configPath ? tree.read(configPath, "utf-8") : null;
|
|
2747
|
-
const result = configText !== null ? applyBoundaryConstraints(configText) : { status: "unrecognized" };
|
|
4115
|
+
const result = configText !== null ? applyBoundaryConstraints(configText, boundaryTags) : { status: "unrecognized" };
|
|
2748
4116
|
if (result.status === "applied") {
|
|
2749
4117
|
eslint = "applied";
|
|
2750
4118
|
if (write && configPath) tree.write(configPath, result.text);
|
|
@@ -2752,7 +4120,7 @@ function runAddBackend(tree, options) {
|
|
|
2752
4120
|
eslint = "already-applied";
|
|
2753
4121
|
} else {
|
|
2754
4122
|
eslint = "degraded-unrecognized";
|
|
2755
|
-
snippet = buildBoundarySnippet();
|
|
4123
|
+
snippet = buildBoundarySnippet(boundaryTags);
|
|
2756
4124
|
}
|
|
2757
4125
|
}
|
|
2758
4126
|
return {
|
|
@@ -2764,7 +4132,8 @@ function runAddBackend(tree, options) {
|
|
|
2764
4132
|
tsconfigPaths,
|
|
2765
4133
|
eslint,
|
|
2766
4134
|
snippet,
|
|
2767
|
-
sharedTypesPackage: wiring.sharedTypesPackageName,
|
|
4135
|
+
sharedTypesPackage: withSharedTypes ? wiring.sharedTypesPackageName : void 0,
|
|
4136
|
+
sharedTypesRoot: withSharedTypes ? sharedTypesRoot : void 0,
|
|
2768
4137
|
config: configOutcome,
|
|
2769
4138
|
packageManager,
|
|
2770
4139
|
...packageManagerNote !== void 0 ? { packageManagerNote } : {}
|
|
@@ -2808,8 +4177,12 @@ function sectionAborted(section) {
|
|
|
2808
4177
|
if (section.report === null) return false;
|
|
2809
4178
|
if (section.report.kind === "fsd") return section.report.report.aborted;
|
|
2810
4179
|
if (section.report.kind === "review") return section.report.report.workflowAction === "aborted";
|
|
4180
|
+
if (section.report.kind === "backend") return section.report.report.refused !== void 0;
|
|
2811
4181
|
return false;
|
|
2812
4182
|
}
|
|
4183
|
+
function failedSections(report) {
|
|
4184
|
+
return report.sections.filter(sectionAborted).map((section) => SECTION_TITLES[section.id]);
|
|
4185
|
+
}
|
|
2813
4186
|
function renderInitReport(report) {
|
|
2814
4187
|
const lines = [];
|
|
2815
4188
|
const backendSuffix = report.backend === "none" ? "" : `, backend: ${report.backend}`;
|
|
@@ -2818,8 +4191,8 @@ function renderInitReport(report) {
|
|
|
2818
4191
|
if (report.refused) {
|
|
2819
4192
|
lines.push("");
|
|
2820
4193
|
lines.push("Refused: the working tree has uncommitted changes.");
|
|
2821
|
-
lines.push(" init only runs against a clean working tree, so a partial run can
|
|
2822
|
-
lines.push("
|
|
4194
|
+
lines.push(" init only runs against a clean working tree, so a partial run can be rolled back exactly");
|
|
4195
|
+
lines.push(" to what was committed \u2014 commit or stash your changes, then rerun.");
|
|
2823
4196
|
return lines.join("\n");
|
|
2824
4197
|
}
|
|
2825
4198
|
for (const section of report.sections) {
|
|
@@ -2871,21 +4244,20 @@ function renderInitReport(report) {
|
|
|
2871
4244
|
if (ranDirty) {
|
|
2872
4245
|
lines.push("");
|
|
2873
4246
|
lines.push("Note: --allowDirty was set and the working tree already had uncommitted changes \u2014");
|
|
2874
|
-
lines.push("
|
|
4247
|
+
lines.push("restoring the files this run modified also reverts your earlier changes to them.");
|
|
2875
4248
|
}
|
|
2876
4249
|
const hadAbort = report.sections.some(sectionAborted);
|
|
2877
4250
|
if (!report.dryRun && hadAbort) {
|
|
2878
4251
|
lines.push("");
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
lines.push(
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
);
|
|
2887
|
-
|
|
2888
|
-
lines.push(" git restore .");
|
|
4252
|
+
lines.push("One or more sections aborted. To undo what this run wrote:");
|
|
4253
|
+
const written = report.written ?? { created: [], modified: [] };
|
|
4254
|
+
if (written.modified.length > 0) {
|
|
4255
|
+
lines.push(` git restore -- ${written.modified.join(" ")}`);
|
|
4256
|
+
if (ranDirty) lines.push(" (--allowDirty: this also reverts the uncommitted changes those files already had.)");
|
|
4257
|
+
}
|
|
4258
|
+
if (written.created.length > 0) {
|
|
4259
|
+
lines.push(" Created by this run (untracked \u2014 delete them yourself; `git clean -n` lists them):");
|
|
4260
|
+
for (const path of written.created) lines.push(` ${path}`);
|
|
2889
4261
|
}
|
|
2890
4262
|
}
|
|
2891
4263
|
return lines.join("\n");
|
|
@@ -2958,48 +4330,34 @@ function recordPresetChoices(tree, choices, notRecorded, write) {
|
|
|
2958
4330
|
if (choices.enforcement !== void 0) fields.push("fsd.enforcement");
|
|
2959
4331
|
if (choices.teamRules !== void 0) fields.push("agentRules.teamRules");
|
|
2960
4332
|
if (choices.aiHosts !== void 0) fields.push("aiHosts");
|
|
4333
|
+
if (choices.frontends !== void 0) fields.push("frontends");
|
|
2961
4334
|
if (fields.length === 0) return { action: "skipped", fields, notRecorded };
|
|
2962
4335
|
const { raw, parsed } = readToolkitConfigFile(tree);
|
|
2963
4336
|
if (raw !== null && parsed === null) return { action: "unrecognized", fields, notRecorded };
|
|
2964
4337
|
const existing = parsed ?? {};
|
|
2965
|
-
const
|
|
2966
|
-
const
|
|
2967
|
-
let changed = false;
|
|
4338
|
+
const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4339
|
+
const edits = [];
|
|
2968
4340
|
for (const key of topLevel) {
|
|
2969
|
-
if (existing[key] !== choices[key])
|
|
2970
|
-
next[key] = choices[key];
|
|
2971
|
-
changed = true;
|
|
2972
|
-
}
|
|
2973
|
-
}
|
|
2974
|
-
if (choices.enforcement !== void 0) {
|
|
2975
|
-
const fsd = asObject(existing["fsd"]);
|
|
2976
|
-
if (fsd["enforcement"] !== choices.enforcement) {
|
|
2977
|
-
next["fsd"] = { ...fsd, enforcement: choices.enforcement };
|
|
2978
|
-
changed = true;
|
|
2979
|
-
}
|
|
2980
|
-
}
|
|
2981
|
-
if (choices.teamRules !== void 0) {
|
|
2982
|
-
const agentRules = asObject(existing["agentRules"]);
|
|
2983
|
-
if (agentRules["teamRules"] !== choices.teamRules) {
|
|
2984
|
-
next["agentRules"] = { ...agentRules, teamRules: choices.teamRules };
|
|
2985
|
-
changed = true;
|
|
2986
|
-
}
|
|
4341
|
+
if (existing[key] !== choices[key]) edits.push([[key], choices[key]]);
|
|
2987
4342
|
}
|
|
4343
|
+
const nested = (parent, key, value) => {
|
|
4344
|
+
const holder = existing[parent];
|
|
4345
|
+
if (isObject(holder) && holder[key] === value) return;
|
|
4346
|
+
edits.push(isObject(holder) ? [[parent, key], value] : [[parent], { [key]: value }]);
|
|
4347
|
+
};
|
|
4348
|
+
if (choices.enforcement !== void 0) nested("fsd", "enforcement", choices.enforcement);
|
|
4349
|
+
if (choices.teamRules !== void 0) nested("agentRules", "teamRules", choices.teamRules);
|
|
2988
4350
|
if (choices.aiHosts !== void 0) {
|
|
2989
4351
|
const recorded = existing["aiHosts"];
|
|
2990
4352
|
if (!Array.isArray(recorded) || recorded.join("\0") !== choices.aiHosts.join("\0")) {
|
|
2991
|
-
|
|
2992
|
-
changed = true;
|
|
4353
|
+
edits.push([["aiHosts"], [...choices.aiHosts]]);
|
|
2993
4354
|
}
|
|
2994
4355
|
}
|
|
2995
|
-
if (
|
|
2996
|
-
|
|
2997
|
-
const indent = raw === null ? " " : detectIndent(raw);
|
|
2998
|
-
const trailingNewline = raw === null || raw.endsWith("\n");
|
|
2999
|
-
const body = JSON.stringify(next, null, indent);
|
|
3000
|
-
tree.write(TOOLKIT_CONFIG_PATH, trailingNewline ? `${body}
|
|
3001
|
-
` : body);
|
|
4356
|
+
if (choices.frontends !== void 0 && JSON.stringify(existing["frontends"]) !== JSON.stringify(choices.frontends)) {
|
|
4357
|
+
edits.push([["frontends"], choices.frontends]);
|
|
3002
4358
|
}
|
|
4359
|
+
if (edits.length === 0) return { action: "unchanged", fields, notRecorded };
|
|
4360
|
+
if (write) tree.write(TOOLKIT_CONFIG_PATH, applyJsonEdits(raw, edits));
|
|
3003
4361
|
return { action: raw === null ? "created" : "updated", fields, notRecorded };
|
|
3004
4362
|
}
|
|
3005
4363
|
function keptReviewProvider(option, config) {
|
|
@@ -3007,13 +4365,32 @@ function keptReviewProvider(option, config) {
|
|
|
3007
4365
|
const hosts = resolveAiHostsOption(option, config);
|
|
3008
4366
|
return hosts[0] === recordedHosts[0] ? recordedReview(config).provider : void 0;
|
|
3009
4367
|
}
|
|
4368
|
+
function pendingContents(tree) {
|
|
4369
|
+
return new Map(tree.listChanges().map((change) => [change.path, change.content?.toString("utf-8") ?? null]));
|
|
4370
|
+
}
|
|
4371
|
+
function writtenSince(before, tree) {
|
|
4372
|
+
const created = [];
|
|
4373
|
+
const modified = [];
|
|
4374
|
+
for (const change of tree.listChanges()) {
|
|
4375
|
+
if (change.type === "DELETE") continue;
|
|
4376
|
+
if (!before.has(change.path)) {
|
|
4377
|
+
(change.type === "CREATE" ? created : modified).push(change.path);
|
|
4378
|
+
} else if (before.get(change.path) !== (change.content?.toString("utf-8") ?? null)) {
|
|
4379
|
+
modified.push(change.path);
|
|
4380
|
+
}
|
|
4381
|
+
}
|
|
4382
|
+
return { created: created.sort(), modified: modified.sort() };
|
|
4383
|
+
}
|
|
3010
4384
|
function runInit(tree, options, deps) {
|
|
4385
|
+
const before = pendingContents(tree);
|
|
3011
4386
|
const recorded = readToolkitConfigFile(tree).parsed;
|
|
3012
4387
|
const preset = options.preset ?? recordedChoice(recorded, "preset", RECORDED_PRESETS);
|
|
3013
4388
|
if (preset === void 0) {
|
|
3014
4389
|
throw new Error("init: pass --preset=quick-start|solo|team (toolkit.config.json records none).");
|
|
3015
4390
|
}
|
|
3016
|
-
const
|
|
4391
|
+
const app = options.frontend === void 0 && recordedChoice(recorded, "frontend", RECORDED_FRONTENDS) === void 0 ? resolveFrontendApp(tree, void 0) : null;
|
|
4392
|
+
const detected = app?.kind === "found" && app.framework !== "generic" ? app.framework : void 0;
|
|
4393
|
+
const frontend = resolveInitFrontend(options.frontend ?? detected, recorded);
|
|
3017
4394
|
const backend = options.backend ?? recordedChoice(recorded, "backend", RECORDED_BACKENDS) ?? "none";
|
|
3018
4395
|
if (frontend === "none" && backend === "none") {
|
|
3019
4396
|
throw new Error("init: --frontend=none is a backend-only workspace \u2014 pass --backend=express|nest.");
|
|
@@ -3043,17 +4420,30 @@ function runInit(tree, options, deps) {
|
|
|
3043
4420
|
});
|
|
3044
4421
|
const sections = [];
|
|
3045
4422
|
const fsdEnabled = frontend !== "none" && (options.fsd ?? config.fsd.enabled);
|
|
4423
|
+
const passedProjects = (Array.isArray(options.project) ? options.project : (options.project ?? "").split(",")).map((name) => name.trim()).filter((name) => name !== "");
|
|
4424
|
+
const projects = passedProjects.length > 0 ? passedProjects : recordedFrontends(recorded);
|
|
3046
4425
|
if (fsdEnabled) {
|
|
3047
|
-
|
|
3048
|
-
const
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
4426
|
+
for (const project of projects.length === 0 ? [void 0] : projects) {
|
|
4427
|
+
const found = project === void 0 ? null : resolveFrontendApp(tree, project);
|
|
4428
|
+
const appFrontend = found?.kind === "found" && found.framework !== "generic" ? found.framework : frontend;
|
|
4429
|
+
try {
|
|
4430
|
+
const report = runAddFsdLite(tree, {
|
|
4431
|
+
frontend: appFrontend,
|
|
4432
|
+
locale,
|
|
4433
|
+
dryRun,
|
|
4434
|
+
enforcement: config.fsd.enforcement,
|
|
4435
|
+
applyEslint: options.applyEslint ?? false,
|
|
4436
|
+
...project === void 0 ? {} : { project }
|
|
4437
|
+
});
|
|
4438
|
+
sections.push({ id: "fsd", enabled: true, report: { kind: "fsd", report } });
|
|
4439
|
+
} catch (error) {
|
|
4440
|
+
sections.push({
|
|
4441
|
+
id: "fsd",
|
|
4442
|
+
enabled: true,
|
|
4443
|
+
report: null,
|
|
4444
|
+
thrownError: error instanceof Error ? error.message : String(error)
|
|
4445
|
+
});
|
|
4446
|
+
}
|
|
3057
4447
|
}
|
|
3058
4448
|
} else {
|
|
3059
4449
|
sections.push({ id: "fsd", enabled: false, report: null });
|
|
@@ -3093,7 +4483,9 @@ function runInit(tree, options, deps) {
|
|
|
3093
4483
|
const report = runAddBackend(tree, {
|
|
3094
4484
|
backend: config.backend,
|
|
3095
4485
|
applyEslint: options.applyEslint ?? false,
|
|
3096
|
-
dryRun
|
|
4486
|
+
dryRun,
|
|
4487
|
+
// add-backend wires one frontend: the one --project names, when it names one
|
|
4488
|
+
...projects.length === 1 ? { frontendProject: projects[0] } : {}
|
|
3097
4489
|
});
|
|
3098
4490
|
sections.push({ id: "backend", enabled: true, report: { kind: "backend", report } });
|
|
3099
4491
|
} else {
|
|
@@ -3115,12 +4507,13 @@ function runInit(tree, options, deps) {
|
|
|
3115
4507
|
} else {
|
|
3116
4508
|
sections.push({ id: "review", enabled: false, report: null });
|
|
3117
4509
|
}
|
|
3118
|
-
const
|
|
3119
|
-
const fsdRan =
|
|
3120
|
-
const fsdApplied =
|
|
4510
|
+
const fsdSections = sections.filter((s) => s.id === "fsd");
|
|
4511
|
+
const fsdRan = fsdSections.some((s) => s.enabled);
|
|
4512
|
+
const fsdApplied = fsdRan && fsdSections.every((s) => s.report?.kind === "fsd" && !s.report.report.aborted);
|
|
3121
4513
|
const agentRulesSection = sections.find((s) => s.id === "agentRules");
|
|
3122
4514
|
const agentRulesRan = agentRulesSection?.enabled === true;
|
|
3123
|
-
const agentRulesApplied = agentRulesSection?.report?.kind === "agentRules" &&
|
|
4515
|
+
const agentRulesApplied = agentRulesSection?.report?.kind === "agentRules" && // a block kept because the user edited it does not carry this run's choices either
|
|
4516
|
+
agentRulesSection.report.report.files.every((f) => f.action !== "unrecognized" && f.action !== "user-modified");
|
|
3124
4517
|
const reviewSection = sections.find((s) => s.id === "review");
|
|
3125
4518
|
const reviewApplied = reviewSection?.report?.kind === "review" && ["created", "updated", "unchanged"].includes(reviewSection.report.report.configAction);
|
|
3126
4519
|
const anyChoiceApplied = fsdApplied || agentRulesApplied || reviewApplied;
|
|
@@ -3149,7 +4542,11 @@ function runInit(tree, options, deps) {
|
|
|
3149
4542
|
packageManager: anyChoiceApplied && knownPackageManager !== null ? knownPackageManager : void 0,
|
|
3150
4543
|
enforcement: fsdApplied ? config.fsd.enforcement : void 0,
|
|
3151
4544
|
teamRules: agentRulesApplied ? config.agentRules.teamRules : void 0,
|
|
3152
|
-
aiHosts: agentRulesApplied ? config.aiHosts : void 0
|
|
4545
|
+
aiHosts: agentRulesApplied ? config.aiHosts : void 0,
|
|
4546
|
+
// with --project, the apps each FSD section set up
|
|
4547
|
+
frontends: fsdApplied && projects.length > 0 ? fsdSections.flatMap(
|
|
4548
|
+
(s) => s.report?.kind === "fsd" && s.report.report.project !== null && s.report.report.sourceRoot !== null ? [{ project: s.report.report.project, framework: s.report.report.frontend, sourceRoot: s.report.report.sourceRoot }] : []
|
|
4549
|
+
) : void 0
|
|
3153
4550
|
},
|
|
3154
4551
|
notRecorded,
|
|
3155
4552
|
!dryRun
|
|
@@ -3164,7 +4561,8 @@ function runInit(tree, options, deps) {
|
|
|
3164
4561
|
allowDirty,
|
|
3165
4562
|
refused: false,
|
|
3166
4563
|
sections,
|
|
3167
|
-
config: configOutcome
|
|
4564
|
+
config: configOutcome,
|
|
4565
|
+
written: dryRun ? void 0 : writtenSince(before, tree)
|
|
3168
4566
|
};
|
|
3169
4567
|
}
|
|
3170
4568
|
function gitIsWorkingTreeClean(root) {
|
|
@@ -3194,13 +4592,21 @@ async function initGenerator(tree, options, deps = { isWorkingTreeClean: gitIsWo
|
|
|
3194
4592
|
const changesBefore = tree.listChanges().length;
|
|
3195
4593
|
const report = runInit(tree, { ...options, dryRun: resolveDryRun(options) }, deps);
|
|
3196
4594
|
console.log(renderInitReport(report));
|
|
4595
|
+
const failed = options.strict === true ? report.refused ? ["the dirty-tree check"] : failedSections(report) : [];
|
|
4596
|
+
const strictFailure = failed.length === 0 ? null : new Error(`init --strict: a section aborted (${failed.join(", ")}) \u2014 see the report above.`);
|
|
4597
|
+
if (strictFailure !== null && (report.dryRun || report.refused)) throw strictFailure;
|
|
3197
4598
|
if (report.dryRun || report.refused) return;
|
|
3198
|
-
|
|
4599
|
+
const wrote = tree.listChanges().length !== changesBefore;
|
|
4600
|
+
if (!wrote) {
|
|
4601
|
+
if (strictFailure !== null) throw strictFailure;
|
|
4602
|
+
return;
|
|
4603
|
+
}
|
|
3199
4604
|
const workflowPaths = collectWrittenWorkflowPaths(report);
|
|
3200
4605
|
const root = tree.root;
|
|
3201
4606
|
const graphOutputFile = join2(tmpdir(), `nx-archweave-init-graph-${Date.now()}.json`);
|
|
3202
4607
|
return () => {
|
|
3203
4608
|
runPostInitValidation({ root, workflowPaths, graphOutputFile }, createProcessExec());
|
|
4609
|
+
if (strictFailure !== null) throw strictFailure;
|
|
3204
4610
|
};
|
|
3205
4611
|
}
|
|
3206
4612
|
var generator_default = initGenerator;
|