@tailor-platform/erp-kit 0.2.0 → 0.2.2
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/CHANGELOG.md +12 -0
- package/README.md +3 -26
- package/dist/cli.mjs +1613 -0
- package/package.json +14 -13
- package/schemas/app-compose/business-flow.yml +3 -0
- package/schemas/app-compose/story.yml +13 -1
- package/schemas/module/command.yml +9 -0
- package/schemas/module/module.yml +4 -0
- package/schemas/module/query.yml +9 -0
- package/skills/erp-kit-app-1-requirements/SKILL.md +8 -14
- package/skills/erp-kit-app-2-requirements-review/SKILL.md +102 -0
- package/skills/erp-kit-app-2-requirements-review/references/best-practices-check.md +66 -0
- package/skills/erp-kit-app-2-requirements-review/references/boundary-consistency-check.md +69 -0
- package/skills/erp-kit-app-2-requirements-review/references/requirements-report-format.md +25 -0
- package/skills/erp-kit-app-3-plan/SKILL.md +157 -0
- package/skills/erp-kit-app-3-plan/references/resolver-extraction.md +107 -0
- package/skills/erp-kit-app-3-plan/references/screen-extraction.md +74 -0
- package/skills/erp-kit-app-3-plan/references/story-extraction.md +86 -0
- package/skills/erp-kit-app-4-plan-review/SKILL.md +177 -0
- package/skills/erp-kit-app-4-plan-review/references/actor-flow-parity.md +73 -0
- package/skills/erp-kit-app-4-plan-review/references/business-flow-story-parity.md +86 -0
- package/skills/erp-kit-app-4-plan-review/references/orphan-detection.md +69 -0
- package/skills/erp-kit-app-4-plan-review/references/parity-report-format.md +52 -0
- package/skills/erp-kit-app-4-plan-review/references/story-resolver-parity.md +83 -0
- package/skills/erp-kit-app-4-plan-review/references/story-screen-parity.md +73 -0
- package/skills/erp-kit-app-5-impl-backend/SKILL.md +105 -0
- package/skills/erp-kit-app-5-impl-backend/references/app-config.md +38 -0
- package/skills/erp-kit-app-5-impl-backend/references/module-wiring.md +48 -0
- package/skills/erp-kit-app-5-impl-backend/references/resolver-patterns.md +68 -0
- package/skills/erp-kit-app-6-impl-frontend/SKILL.md +74 -0
- package/skills/erp-kit-app-6-impl-frontend/references/pages.md +160 -0
- package/skills/erp-kit-app-7-impl-review/SKILL.md +176 -0
- package/skills/erp-kit-app-7-impl-review/references/impl-parity-report-format.md +52 -0
- package/skills/erp-kit-app-7-impl-review/references/module-wiring-parity.md +84 -0
- package/skills/erp-kit-app-7-impl-review/references/resolver-doc-code-parity.md +86 -0
- package/skills/erp-kit-app-7-impl-review/references/screen-doc-code-parity.md +86 -0
- package/skills/erp-kit-module-1-requirements/SKILL.md +126 -0
- package/skills/erp-kit-module-1-requirements/references/boundary-analysis.md +51 -0
- package/skills/erp-kit-module-1-requirements/references/erp-research.md +57 -0
- package/skills/erp-kit-module-1-requirements/references/feature-doc.md +61 -0
- package/skills/erp-kit-module-2-requirements-review/SKILL.md +112 -0
- package/skills/erp-kit-module-2-requirements-review/references/best-practices-check.md +79 -0
- package/skills/erp-kit-module-2-requirements-review/references/boundary-consistency-check.md +70 -0
- package/skills/erp-kit-module-2-requirements-review/references/requirements-report-format.md +25 -0
- package/skills/erp-kit-module-3-plan/SKILL.md +107 -0
- package/skills/erp-kit-module-3-plan/references/command-extraction.md +87 -0
- package/skills/erp-kit-module-3-plan/references/model-extraction.md +72 -0
- package/skills/erp-kit-module-3-plan/references/query-extraction.md +59 -0
- package/skills/erp-kit-module-4-plan-review/SKILL.md +158 -0
- package/skills/erp-kit-module-4-plan-review/references/command-model-consistency.md +46 -0
- package/skills/erp-kit-module-4-plan-review/references/feature-command-parity.md +97 -0
- package/skills/erp-kit-module-4-plan-review/references/feature-model-parity.md +47 -0
- package/skills/erp-kit-module-4-plan-review/references/feature-query-parity.md +70 -0
- package/skills/erp-kit-module-4-plan-review/references/parity-report-format.md +52 -0
- package/skills/erp-kit-module-5-impl/SKILL.md +118 -0
- package/skills/erp-kit-module-5-impl/references/command-impl.md +68 -0
- package/skills/erp-kit-module-5-impl/references/exports.md +10 -0
- package/skills/erp-kit-module-5-impl/references/model-impl.md +45 -0
- package/skills/erp-kit-module-5-impl/references/query-impl.md +53 -0
- package/skills/erp-kit-module-6-impl-review/SKILL.md +187 -0
- package/skills/erp-kit-module-6-impl-review/references/command-doc-code-parity.md +92 -0
- package/skills/erp-kit-module-6-impl-review/references/command-doc-test-parity.md +93 -0
- package/skills/erp-kit-module-6-impl-review/references/error-implementation-parity.md +95 -0
- package/skills/{erp-kit-module-5-impl-review → erp-kit-module-6-impl-review}/references/errors.md +1 -1
- package/skills/erp-kit-module-6-impl-review/references/impl-parity-report-format.md +52 -0
- package/skills/erp-kit-module-6-impl-review/references/model-doc-code-parity.md +80 -0
- package/skills/erp-kit-module-shared/SKILL.md +1 -1
- package/skills/erp-kit-module-shared/references/commands.md +1 -1
- package/skills/erp-kit-module-shared/references/errors.md +12 -9
- package/skills/erp-kit-module-shared/references/queries.md +109 -36
- package/skills/erp-kit-module-shared/references/testing.md +10 -0
- package/skills/erp-kit-update/SKILL.md +2 -2
- package/src/app.ts +1 -1
- package/src/commands/check.ts +1 -1
- package/src/commands/index.ts +16 -5
- package/src/commands/init.test.ts +22 -69
- package/src/commands/init.ts +28 -115
- package/src/commands/lib/distribute.test.ts +126 -0
- package/src/commands/lib/distribute.ts +129 -0
- package/src/commands/parse-doc-test-cases.ts +55 -0
- package/src/commands/scaffold.test.ts +74 -33
- package/src/commands/scaffold.ts +54 -18
- package/src/commands/sync-check.test.ts +173 -0
- package/src/commands/sync-check.ts +103 -2
- package/src/commands/update.test.ts +87 -0
- package/src/commands/update.ts +41 -0
- package/src/generator/generate-code.test.ts +23 -12
- package/src/generator/generate-code.ts +22 -16
- package/src/integration.test.ts +1 -1
- package/src/module.ts +20 -65
- package/src/modules/item-management/README.md +8 -0
- package/src/modules/item-management/command/activateItem.generated.ts +1 -1
- package/src/modules/item-management/command/activateItem.test.ts +12 -18
- package/src/modules/item-management/command/activateItem.ts +9 -5
- package/src/modules/item-management/command/assignItemToTaxonomy.generated.ts +1 -1
- package/src/modules/item-management/command/assignItemToTaxonomy.test.ts +10 -24
- package/src/modules/item-management/command/assignItemToTaxonomy.ts +19 -16
- package/src/modules/item-management/command/createItem.generated.ts +1 -1
- package/src/modules/item-management/command/createItem.test.ts +11 -11
- package/src/modules/item-management/command/createItem.ts +16 -7
- package/src/modules/item-management/command/createTaxonomyNode.generated.ts +1 -1
- package/src/modules/item-management/command/createTaxonomyNode.test.ts +9 -9
- package/src/modules/item-management/command/createTaxonomyNode.ts +33 -14
- package/src/modules/item-management/command/deactivateItem.generated.ts +1 -1
- package/src/modules/item-management/command/deactivateItem.test.ts +12 -18
- package/src/modules/item-management/command/deactivateItem.ts +9 -5
- package/src/modules/item-management/command/deleteItem.generated.ts +1 -1
- package/src/modules/item-management/command/deleteItem.test.ts +10 -16
- package/src/modules/item-management/command/deleteItem.ts +9 -5
- package/src/modules/item-management/command/deleteTaxonomyNode.generated.ts +1 -1
- package/src/modules/item-management/command/deleteTaxonomyNode.test.ts +10 -16
- package/src/modules/item-management/command/deleteTaxonomyNode.ts +22 -12
- package/src/modules/item-management/command/moveTaxonomyNode.generated.ts +1 -1
- package/src/modules/item-management/command/moveTaxonomyNode.test.ts +10 -10
- package/src/modules/item-management/command/moveTaxonomyNode.ts +63 -19
- package/src/modules/item-management/command/reactivateItem.generated.ts +1 -1
- package/src/modules/item-management/command/reactivateItem.test.ts +12 -18
- package/src/modules/item-management/command/reactivateItem.ts +9 -5
- package/src/modules/item-management/command/removeItemFromTaxonomy.generated.ts +1 -1
- package/src/modules/item-management/command/removeItemFromTaxonomy.test.ts +9 -16
- package/src/modules/item-management/command/removeItemFromTaxonomy.ts +11 -6
- package/src/modules/item-management/command/updateItem.generated.ts +1 -1
- package/src/modules/item-management/command/updateItem.test.ts +16 -16
- package/src/modules/item-management/command/updateItem.ts +11 -6
- package/src/modules/item-management/command/updateTaxonomyNode.generated.ts +1 -1
- package/src/modules/item-management/command/updateTaxonomyNode.test.ts +14 -20
- package/src/modules/item-management/command/updateTaxonomyNode.ts +9 -6
- package/src/modules/item-management/docs/commands/ActivateItem.md +8 -0
- package/src/modules/item-management/docs/commands/AssignItemToTaxonomy.md +7 -0
- package/src/modules/item-management/docs/commands/CreateItem.md +10 -0
- package/src/modules/item-management/docs/commands/CreateTaxonomyNode.md +9 -0
- package/src/modules/item-management/docs/commands/DeactivateItem.md +8 -0
- package/src/modules/item-management/docs/commands/DeleteItem.md +7 -0
- package/src/modules/item-management/docs/commands/DeleteTaxonomyNode.md +7 -0
- package/src/modules/item-management/docs/commands/MoveTaxonomyNode.md +10 -0
- package/src/modules/item-management/docs/commands/ReactivateItem.md +8 -0
- package/src/modules/item-management/docs/commands/RemoveItemFromTaxonomy.md +5 -0
- package/src/modules/item-management/docs/commands/UpdateItem.md +15 -0
- package/src/modules/item-management/docs/commands/UpdateTaxonomyNode.md +9 -0
- package/src/modules/item-management/docs/queries/CalculateNodeDepth.md +8 -0
- package/src/modules/item-management/docs/queries/CalculateSubtreeDepth.md +7 -0
- package/src/modules/item-management/docs/queries/DetectCircularReference.md +9 -0
- package/src/modules/item-management/docs/queries/GetItem.md +9 -0
- package/src/modules/item-management/docs/queries/GetItemTaxonomyAssignment.md +5 -0
- package/src/modules/item-management/docs/queries/GetTaxonomyNode.md +7 -0
- package/src/modules/item-management/docs/queries/GetTaxonomyNodeAssignments.md +5 -0
- package/src/modules/item-management/docs/queries/GetTaxonomyNodeChildren.md +6 -0
- package/src/modules/item-management/index.ts +0 -51
- package/src/modules/item-management/lib/errors.generated.ts +24 -24
- package/src/modules/item-management/lib/permissions.generated.ts +1 -1
- package/src/modules/item-management/lib/types.ts +1 -1
- package/src/modules/item-management/module.ts +1 -1
- package/src/modules/item-management/query/calculateNodeDepth.generated.ts +1 -1
- package/src/modules/item-management/query/calculateNodeDepth.test.ts +21 -6
- package/src/modules/item-management/query/calculateNodeDepth.ts +2 -2
- package/src/modules/item-management/query/calculateSubtreeDepth.generated.ts +1 -1
- package/src/modules/item-management/query/calculateSubtreeDepth.test.ts +17 -5
- package/src/modules/item-management/query/calculateSubtreeDepth.ts +2 -2
- package/src/modules/item-management/query/detectCircularReference.generated.ts +1 -1
- package/src/modules/item-management/query/detectCircularReference.test.ts +25 -7
- package/src/modules/item-management/query/detectCircularReference.ts +4 -4
- package/src/modules/item-management/query/getItem.generated.ts +1 -1
- package/src/modules/item-management/query/getItem.test.ts +25 -7
- package/src/modules/item-management/query/getItem.ts +2 -2
- package/src/modules/item-management/query/getItemTaxonomyAssignment.generated.ts +1 -1
- package/src/modules/item-management/query/getItemTaxonomyAssignment.test.ts +9 -3
- package/src/modules/item-management/query/getItemTaxonomyAssignment.ts +2 -2
- package/src/modules/item-management/query/getTaxonomyNode.generated.ts +1 -1
- package/src/modules/item-management/query/getTaxonomyNode.test.ts +17 -5
- package/src/modules/item-management/query/getTaxonomyNode.ts +2 -2
- package/src/modules/item-management/query/getTaxonomyNodeAssignments.generated.ts +1 -1
- package/src/modules/item-management/query/getTaxonomyNodeAssignments.test.ts +9 -3
- package/src/modules/item-management/query/getTaxonomyNodeAssignments.ts +2 -2
- package/src/modules/item-management/query/getTaxonomyNodeChildren.generated.ts +1 -1
- package/src/modules/item-management/query/getTaxonomyNodeChildren.test.ts +13 -4
- package/src/modules/item-management/query/getTaxonomyNodeChildren.ts +2 -2
- package/src/modules/item-management/tailor.config.ts +6 -4
- package/src/modules/item-management/tailor.d.ts +13 -0
- package/src/modules/primitives/README.md +8 -0
- package/src/modules/primitives/command/activateCategory.generated.ts +1 -1
- package/src/modules/primitives/command/activateCategory.test.ts +8 -18
- package/src/modules/primitives/command/activateCategory.ts +9 -5
- package/src/modules/primitives/command/activateCurrency.generated.ts +1 -1
- package/src/modules/primitives/command/activateCurrency.test.ts +8 -18
- package/src/modules/primitives/command/activateCurrency.ts +9 -5
- package/src/modules/primitives/command/activateUnit.generated.ts +1 -1
- package/src/modules/primitives/command/activateUnit.test.ts +8 -15
- package/src/modules/primitives/command/activateUnit.ts +9 -5
- package/src/modules/primitives/command/createCategory.generated.ts +1 -1
- package/src/modules/primitives/command/createCategory.test.ts +29 -44
- package/src/modules/primitives/command/createCategory.ts +9 -5
- package/src/modules/primitives/command/createCurrency.generated.ts +1 -1
- package/src/modules/primitives/command/createCurrency.test.ts +53 -78
- package/src/modules/primitives/command/createCurrency.ts +9 -6
- package/src/modules/primitives/command/createExchangeRate.generated.ts +1 -1
- package/src/modules/primitives/command/createExchangeRate.test.ts +59 -97
- package/src/modules/primitives/command/createExchangeRate.ts +13 -7
- package/src/modules/primitives/command/createUnit.generated.ts +1 -1
- package/src/modules/primitives/command/createUnit.test.ts +59 -90
- package/src/modules/primitives/command/createUnit.ts +9 -6
- package/src/modules/primitives/command/deactivateCategory.generated.ts +1 -1
- package/src/modules/primitives/command/deactivateCategory.test.ts +15 -33
- package/src/modules/primitives/command/deactivateCategory.ts +9 -5
- package/src/modules/primitives/command/deactivateCurrency.generated.ts +1 -1
- package/src/modules/primitives/command/deactivateCurrency.test.ts +12 -26
- package/src/modules/primitives/command/deactivateCurrency.ts +9 -5
- package/src/modules/primitives/command/deactivateUnit.generated.ts +1 -1
- package/src/modules/primitives/command/deactivateUnit.test.ts +15 -30
- package/src/modules/primitives/command/deactivateUnit.ts +14 -7
- package/src/modules/primitives/command/setBaseCurrency.generated.ts +1 -1
- package/src/modules/primitives/command/setBaseCurrency.test.ts +18 -40
- package/src/modules/primitives/command/setBaseCurrency.ts +15 -7
- package/src/modules/primitives/command/setReferenceUnit.generated.ts +1 -1
- package/src/modules/primitives/command/setReferenceUnit.test.ts +22 -44
- package/src/modules/primitives/command/setReferenceUnit.ts +21 -9
- package/src/modules/primitives/docs/commands/ActivateCategory.md +6 -0
- package/src/modules/primitives/docs/commands/ActivateCurrency.md +6 -0
- package/src/modules/primitives/docs/commands/ActivateUnit.md +6 -0
- package/src/modules/primitives/docs/commands/CreateCategory.md +6 -0
- package/src/modules/primitives/docs/commands/CreateCurrency.md +10 -0
- package/src/modules/primitives/docs/commands/CreateExchangeRate.md +11 -0
- package/src/modules/primitives/docs/commands/CreateUnit.md +10 -0
- package/src/modules/primitives/docs/commands/DeactivateCategory.md +7 -0
- package/src/modules/primitives/docs/commands/DeactivateCurrency.md +7 -0
- package/src/modules/primitives/docs/commands/DeactivateUnit.md +7 -0
- package/src/modules/primitives/docs/commands/SetBaseCurrency.md +7 -0
- package/src/modules/primitives/docs/commands/SetReferenceUnit.md +7 -0
- package/src/modules/primitives/docs/queries/ConvertAmount.md +14 -0
- package/src/modules/primitives/docs/queries/ConvertQuantity.md +13 -0
- package/src/modules/primitives/docs/queries/GetBaseCurrency.md +5 -0
- package/src/modules/primitives/docs/queries/GetCurrency.md +7 -0
- package/src/modules/primitives/docs/queries/GetUnit.md +7 -0
- package/src/modules/primitives/docs/queries/GetUoMCategory.md +7 -0
- package/src/modules/primitives/docs/queries/ListUnitsByCategory.md +15 -5
- package/src/modules/primitives/index.ts +0 -49
- package/src/modules/primitives/lib/errors.generated.ts +23 -23
- package/src/modules/primitives/lib/permissions.generated.ts +1 -1
- package/src/modules/primitives/lib/types.ts +1 -1
- package/src/modules/primitives/module.ts +1 -1
- package/src/modules/primitives/query/convertAmount.generated.ts +1 -1
- package/src/modules/primitives/query/convertAmount.test.ts +110 -77
- package/src/modules/primitives/query/convertAmount.ts +61 -47
- package/src/modules/primitives/query/convertQuantity.generated.ts +1 -1
- package/src/modules/primitives/query/convertQuantity.test.ts +99 -69
- package/src/modules/primitives/query/convertQuantity.ts +12 -10
- package/src/modules/primitives/query/getBaseCurrency.generated.ts +1 -1
- package/src/modules/primitives/query/getBaseCurrency.test.ts +10 -4
- package/src/modules/primitives/query/getBaseCurrency.ts +2 -2
- package/src/modules/primitives/query/getCurrency.generated.ts +1 -1
- package/src/modules/primitives/query/getCurrency.test.ts +17 -5
- package/src/modules/primitives/query/getCurrency.ts +2 -2
- package/src/modules/primitives/query/getUnit.generated.ts +1 -1
- package/src/modules/primitives/query/getUnit.test.ts +17 -5
- package/src/modules/primitives/query/getUnit.ts +2 -2
- package/src/modules/primitives/query/getUoMCategory.generated.ts +1 -1
- package/src/modules/primitives/query/getUoMCategory.test.ts +17 -5
- package/src/modules/primitives/query/getUoMCategory.ts +2 -2
- package/src/modules/primitives/query/listUnitsByCategory.generated.ts +1 -1
- package/src/modules/primitives/query/listUnitsByCategory.test.ts +80 -0
- package/src/modules/primitives/query/listUnitsByCategory.ts +19 -3
- package/src/modules/primitives/tailor.config.ts +6 -4
- package/src/modules/primitives/tailor.d.ts +13 -0
- package/src/modules/product-management/README.md +52 -0
- package/src/modules/product-management/command/activateProduct.generated.ts +6 -0
- package/src/modules/product-management/command/activateProduct.test.ts +40 -0
- package/src/modules/product-management/command/activateProduct.ts +42 -0
- package/src/modules/product-management/command/assignProductToCategory.generated.ts +6 -0
- package/src/modules/product-management/command/assignProductToCategory.test.ts +90 -0
- package/src/modules/product-management/command/assignProductToCategory.ts +62 -0
- package/src/modules/product-management/command/createProduct.generated.ts +6 -0
- package/src/modules/product-management/command/createProduct.test.ts +149 -0
- package/src/modules/product-management/command/createProduct.ts +73 -0
- package/src/modules/product-management/command/createProductAttribute.generated.ts +6 -0
- package/src/modules/product-management/command/createProductAttribute.test.ts +70 -0
- package/src/modules/product-management/command/createProductAttribute.ts +53 -0
- package/src/modules/product-management/command/createProductAttributeValue.generated.ts +6 -0
- package/src/modules/product-management/command/createProductAttributeValue.test.ts +68 -0
- package/src/modules/product-management/command/createProductAttributeValue.ts +63 -0
- package/src/modules/product-management/command/createProductCategory.generated.ts +6 -0
- package/src/modules/product-management/command/createProductCategory.test.ts +135 -0
- package/src/modules/product-management/command/createProductCategory.ts +82 -0
- package/src/modules/product-management/command/deactivateProduct.generated.ts +6 -0
- package/src/modules/product-management/command/deactivateProduct.test.ts +40 -0
- package/src/modules/product-management/command/deactivateProduct.ts +42 -0
- package/src/modules/product-management/command/deleteProduct.generated.ts +6 -0
- package/src/modules/product-management/command/deleteProduct.test.ts +42 -0
- package/src/modules/product-management/command/deleteProduct.ts +42 -0
- package/src/modules/product-management/command/deleteProductAttribute.generated.ts +6 -0
- package/src/modules/product-management/command/deleteProductAttribute.test.ts +49 -0
- package/src/modules/product-management/command/deleteProductAttribute.ts +45 -0
- package/src/modules/product-management/command/deleteProductAttributeValue.generated.ts +6 -0
- package/src/modules/product-management/command/deleteProductAttributeValue.test.ts +71 -0
- package/src/modules/product-management/command/deleteProductAttributeValue.ts +68 -0
- package/src/modules/product-management/command/deleteProductCategory.generated.ts +6 -0
- package/src/modules/product-management/command/deleteProductCategory.test.ts +74 -0
- package/src/modules/product-management/command/deleteProductCategory.ts +53 -0
- package/src/modules/product-management/command/generateVariants.generated.ts +6 -0
- package/src/modules/product-management/command/generateVariants.test.ts +365 -0
- package/src/modules/product-management/command/generateVariants.ts +168 -0
- package/src/modules/product-management/command/moveProductCategory.generated.ts +6 -0
- package/src/modules/product-management/command/moveProductCategory.test.ts +170 -0
- package/src/modules/product-management/command/moveProductCategory.ts +124 -0
- package/src/modules/product-management/command/reactivateProduct.generated.ts +6 -0
- package/src/modules/product-management/command/reactivateProduct.test.ts +40 -0
- package/src/modules/product-management/command/reactivateProduct.ts +42 -0
- package/src/modules/product-management/command/removeProductFromCategory.generated.ts +6 -0
- package/src/modules/product-management/command/removeProductFromCategory.test.ts +42 -0
- package/src/modules/product-management/command/removeProductFromCategory.ts +32 -0
- package/src/modules/product-management/command/setProductAttributeAssignment.generated.ts +6 -0
- package/src/modules/product-management/command/setProductAttributeAssignment.test.ts +206 -0
- package/src/modules/product-management/command/setProductAttributeAssignment.ts +102 -0
- package/src/modules/product-management/command/updateProduct.generated.ts +6 -0
- package/src/modules/product-management/command/updateProduct.test.ts +168 -0
- package/src/modules/product-management/command/updateProduct.ts +95 -0
- package/src/modules/product-management/command/updateProductAttribute.generated.ts +6 -0
- package/src/modules/product-management/command/updateProductAttribute.test.ts +101 -0
- package/src/modules/product-management/command/updateProductAttribute.ts +68 -0
- package/src/modules/product-management/command/updateProductAttributeValue.generated.ts +6 -0
- package/src/modules/product-management/command/updateProductAttributeValue.test.ts +80 -0
- package/src/modules/product-management/command/updateProductAttributeValue.ts +58 -0
- package/src/modules/product-management/command/updateProductCategory.generated.ts +6 -0
- package/src/modules/product-management/command/updateProductCategory.test.ts +80 -0
- package/src/modules/product-management/command/updateProductCategory.ts +66 -0
- package/src/modules/product-management/db/product.ts +47 -0
- package/src/modules/product-management/db/productAttribute.ts +26 -0
- package/src/modules/product-management/db/productAttributeAssignment.ts +58 -0
- package/src/modules/product-management/db/productAttributeValue.ts +39 -0
- package/src/modules/product-management/db/productCategory.ts +34 -0
- package/src/modules/product-management/db/productCategoryAssignment.ts +49 -0
- package/src/modules/product-management/db/productVariant.ts +52 -0
- package/src/modules/product-management/docs/commands/ActivateProduct.md +39 -0
- package/src/modules/product-management/docs/commands/AssignProductToCategory.md +43 -0
- package/src/modules/product-management/docs/commands/CreateProduct.md +48 -0
- package/src/modules/product-management/docs/commands/CreateProductAttribute.md +39 -0
- package/src/modules/product-management/docs/commands/CreateProductAttributeValue.md +42 -0
- package/src/modules/product-management/docs/commands/CreateProductCategory.md +54 -0
- package/src/modules/product-management/docs/commands/DeactivateProduct.md +39 -0
- package/src/modules/product-management/docs/commands/DeleteProduct.md +42 -0
- package/src/modules/product-management/docs/commands/DeleteProductAttribute.md +39 -0
- package/src/modules/product-management/docs/commands/DeleteProductAttributeValue.md +42 -0
- package/src/modules/product-management/docs/commands/DeleteProductCategory.md +43 -0
- package/src/modules/product-management/docs/commands/GenerateVariants.md +68 -0
- package/src/modules/product-management/docs/commands/MoveProductCategory.md +54 -0
- package/src/modules/product-management/docs/commands/ReactivateProduct.md +38 -0
- package/src/modules/product-management/docs/commands/RemoveProductFromCategory.md +34 -0
- package/src/modules/product-management/docs/commands/SetProductAttributeAssignment.md +62 -0
- package/src/modules/product-management/docs/commands/UpdateProduct.md +61 -0
- package/src/modules/product-management/docs/commands/UpdateProductAttribute.md +46 -0
- package/src/modules/product-management/docs/commands/UpdateProductAttributeValue.md +47 -0
- package/src/modules/product-management/docs/commands/UpdateProductCategory.md +46 -0
- package/src/modules/product-management/docs/features/attribute-management.md +48 -0
- package/src/modules/product-management/docs/features/product-category.md +71 -0
- package/src/modules/product-management/docs/features/product-lifecycle.md +66 -0
- package/src/modules/product-management/docs/features/variant-generation.md +77 -0
- package/src/modules/product-management/docs/models/Product.md +58 -0
- package/src/modules/product-management/docs/models/ProductAttribute.md +37 -0
- package/src/modules/product-management/docs/models/ProductAttributeAssignment.md +41 -0
- package/src/modules/product-management/docs/models/ProductAttributeValue.md +40 -0
- package/src/modules/product-management/docs/models/ProductCategory.md +46 -0
- package/src/modules/product-management/docs/models/ProductCategoryAssignment.md +37 -0
- package/src/modules/product-management/docs/models/ProductVariant.md +41 -0
- package/src/modules/product-management/docs/queries/CalculateCategoryDepth.md +47 -0
- package/src/modules/product-management/docs/queries/DetectCategoryCircularReference.md +51 -0
- package/src/modules/product-management/docs/queries/GetProduct.md +42 -0
- package/src/modules/product-management/docs/queries/GetProductAttribute.md +42 -0
- package/src/modules/product-management/docs/queries/GetProductAttributeAssignment.md +34 -0
- package/src/modules/product-management/docs/queries/GetProductAttributeValue.md +40 -0
- package/src/modules/product-management/docs/queries/GetProductCategory.md +42 -0
- package/src/modules/product-management/docs/queries/GetProductCategoryAssignment.md +34 -0
- package/src/modules/product-management/docs/queries/GetProductVariant.md +41 -0
- package/src/modules/product-management/docs/queries/ListAttributeAssignmentsByAttribute.md +34 -0
- package/src/modules/product-management/docs/queries/ListCategoryAssignmentsByProduct.md +35 -0
- package/src/modules/product-management/docs/queries/ListProductAttributeAssignments.md +34 -0
- package/src/modules/product-management/docs/queries/ListProductAttributeValues.md +36 -0
- package/src/modules/product-management/docs/queries/ListProductCategoryAssignments.md +34 -0
- package/src/modules/product-management/docs/queries/ListProductCategoryChildren.md +34 -0
- package/src/modules/product-management/docs/queries/ListProductVariants.md +34 -0
- package/src/modules/product-management/generated/enums.ts +9 -0
- package/src/modules/product-management/generated/kysely-tailordb.ts +100 -0
- package/src/modules/product-management/index.ts +2 -0
- package/src/modules/product-management/lib/_db_deps.ts +17 -0
- package/src/modules/product-management/lib/errors.generated.ts +152 -0
- package/src/modules/product-management/lib/permissions.generated.ts +25 -0
- package/src/modules/product-management/lib/types.ts +51 -0
- package/src/modules/product-management/module.ts +201 -0
- package/src/modules/product-management/query/calculateCategoryDepth.generated.ts +5 -0
- package/src/modules/product-management/query/calculateCategoryDepth.test.ts +72 -0
- package/src/modules/product-management/query/calculateCategoryDepth.ts +37 -0
- package/src/modules/product-management/query/detectCategoryCircularReference.generated.ts +5 -0
- package/src/modules/product-management/query/detectCategoryCircularReference.test.ts +72 -0
- package/src/modules/product-management/query/detectCategoryCircularReference.ts +44 -0
- package/src/modules/product-management/query/getProduct.generated.ts +5 -0
- package/src/modules/product-management/query/getProduct.test.ts +59 -0
- package/src/modules/product-management/query/getProduct.ts +18 -0
- package/src/modules/product-management/query/getProductAttribute.generated.ts +5 -0
- package/src/modules/product-management/query/getProductAttribute.test.ts +59 -0
- package/src/modules/product-management/query/getProductAttribute.ts +18 -0
- package/src/modules/product-management/query/getProductAttributeAssignment.generated.ts +5 -0
- package/src/modules/product-management/query/getProductAttributeAssignment.test.ts +37 -0
- package/src/modules/product-management/query/getProductAttributeAssignment.ts +18 -0
- package/src/modules/product-management/query/getProductAttributeValue.generated.ts +5 -0
- package/src/modules/product-management/query/getProductAttributeValue.test.ts +31 -0
- package/src/modules/product-management/query/getProductAttributeValue.ts +16 -0
- package/src/modules/product-management/query/getProductCategory.generated.ts +5 -0
- package/src/modules/product-management/query/getProductCategory.test.ts +59 -0
- package/src/modules/product-management/query/getProductCategory.ts +18 -0
- package/src/modules/product-management/query/getProductCategoryAssignment.generated.ts +5 -0
- package/src/modules/product-management/query/getProductCategoryAssignment.test.ts +37 -0
- package/src/modules/product-management/query/getProductCategoryAssignment.ts +18 -0
- package/src/modules/product-management/query/getProductVariant.generated.ts +5 -0
- package/src/modules/product-management/query/getProductVariant.test.ts +43 -0
- package/src/modules/product-management/query/getProductVariant.ts +20 -0
- package/src/modules/product-management/query/listAttributeAssignmentsByAttribute.generated.ts +5 -0
- package/src/modules/product-management/query/listAttributeAssignmentsByAttribute.test.ts +31 -0
- package/src/modules/product-management/query/listAttributeAssignmentsByAttribute.ts +16 -0
- package/src/modules/product-management/query/listCategoryAssignmentsByProduct.generated.ts +5 -0
- package/src/modules/product-management/query/listCategoryAssignmentsByProduct.test.ts +31 -0
- package/src/modules/product-management/query/listCategoryAssignmentsByProduct.ts +16 -0
- package/src/modules/product-management/query/listProductAttributeAssignments.generated.ts +5 -0
- package/src/modules/product-management/query/listProductAttributeAssignments.test.ts +31 -0
- package/src/modules/product-management/query/listProductAttributeAssignments.ts +16 -0
- package/src/modules/product-management/query/listProductAttributeValues.generated.ts +5 -0
- package/src/modules/product-management/query/listProductAttributeValues.test.ts +31 -0
- package/src/modules/product-management/query/listProductAttributeValues.ts +17 -0
- package/src/modules/product-management/query/listProductCategoryAssignments.generated.ts +5 -0
- package/src/modules/product-management/query/listProductCategoryAssignments.test.ts +31 -0
- package/src/modules/product-management/query/listProductCategoryAssignments.ts +16 -0
- package/src/modules/product-management/query/listProductCategoryChildren.generated.ts +5 -0
- package/src/modules/product-management/query/listProductCategoryChildren.test.ts +31 -0
- package/src/modules/product-management/query/listProductCategoryChildren.ts +16 -0
- package/src/modules/product-management/query/listProductVariants.generated.ts +5 -0
- package/src/modules/product-management/query/listProductVariants.test.ts +31 -0
- package/src/modules/product-management/query/listProductVariants.ts +16 -0
- package/src/modules/product-management/tailor.config.ts +13 -0
- package/src/modules/product-management/tailor.d.ts +13 -0
- package/src/modules/product-management/testing/fixtures.ts +151 -0
- package/src/modules/user-management/README.md +9 -3
- package/src/modules/user-management/command/activateUser.generated.ts +1 -1
- package/src/modules/user-management/command/activateUser.test.ts +12 -65
- package/src/modules/user-management/command/activateUser.ts +5 -20
- package/src/modules/user-management/command/assignPermissionToRole.generated.ts +1 -1
- package/src/modules/user-management/command/assignPermissionToRole.test.ts +25 -60
- package/src/modules/user-management/command/assignPermissionToRole.ts +5 -24
- package/src/modules/user-management/command/assignRoleToUser.generated.ts +1 -1
- package/src/modules/user-management/command/assignRoleToUser.test.ts +35 -87
- package/src/modules/user-management/command/assignRoleToUser.ts +5 -24
- package/src/modules/user-management/command/createPermission.generated.ts +1 -1
- package/src/modules/user-management/command/createPermission.test.ts +23 -33
- package/src/modules/user-management/command/createPermission.ts +4 -5
- package/src/modules/user-management/command/createRole.generated.ts +1 -1
- package/src/modules/user-management/command/createRole.test.ts +17 -27
- package/src/modules/user-management/command/createRole.ts +4 -5
- package/src/modules/user-management/command/createUser.generated.ts +1 -1
- package/src/modules/user-management/command/createUser.test.ts +31 -118
- package/src/modules/user-management/command/createUser.ts +7 -25
- package/src/modules/user-management/command/deactivateUser.generated.ts +1 -1
- package/src/modules/user-management/command/deactivateUser.test.ts +12 -65
- package/src/modules/user-management/command/deactivateUser.ts +6 -21
- package/src/modules/user-management/command/reactivateUser.generated.ts +1 -1
- package/src/modules/user-management/command/reactivateUser.test.ts +13 -66
- package/src/modules/user-management/command/reactivateUser.ts +5 -20
- package/src/modules/user-management/command/revokePermissionFromRole.generated.ts +1 -1
- package/src/modules/user-management/command/revokePermissionFromRole.test.ts +24 -62
- package/src/modules/user-management/command/revokePermissionFromRole.ts +5 -24
- package/src/modules/user-management/command/revokeRoleFromUser.generated.ts +1 -1
- package/src/modules/user-management/command/revokeRoleFromUser.test.ts +24 -60
- package/src/modules/user-management/command/revokeRoleFromUser.ts +5 -24
- package/src/modules/user-management/docs/commands/ActivateUser.md +7 -0
- package/src/modules/user-management/docs/commands/AssignPermissionToRole.md +7 -0
- package/src/modules/user-management/docs/commands/AssignRoleToUser.md +9 -0
- package/src/modules/user-management/docs/commands/CreatePermission.md +12 -0
- package/src/modules/user-management/docs/commands/CreateRole.md +9 -0
- package/src/modules/user-management/docs/commands/CreateUser.md +11 -0
- package/src/modules/user-management/docs/commands/DeactivateUser.md +7 -0
- package/src/modules/user-management/docs/commands/ReactivateUser.md +7 -0
- package/src/modules/user-management/docs/commands/RevokePermissionFromRole.md +7 -0
- package/src/modules/user-management/docs/commands/RevokeRoleFromUser.md +7 -0
- package/src/modules/user-management/index.ts +0 -30
- package/src/modules/user-management/lib/errors.generated.ts +14 -14
- package/src/modules/user-management/lib/permissions.generated.ts +1 -2
- package/src/modules/user-management/lib/recomputeUserPermissions.ts +4 -3
- package/src/modules/user-management/lib/types.ts +1 -1
- package/src/modules/user-management/module.ts +2 -7
- package/src/modules/user-management/tailor.config.ts +6 -4
- package/src/modules/user-management/tailor.d.ts +13 -0
- package/src/modules/user-management/testing/fixtures.ts +1 -20
- package/src/schemas.ts +1 -1
- package/src/{modules/shared → shared}/defineCommand.test.ts +23 -7
- package/src/{modules/shared → shared}/defineCommand.ts +19 -10
- package/src/{modules/shared/internal.ts → shared/index.ts} +9 -1
- package/src/shared/pagination.test.ts +43 -0
- package/src/shared/pagination.ts +22 -0
- package/src/{modules/shared → shared}/types.ts +13 -0
- package/src/{modules/testing → testing}/index.ts +14 -7
- package/src/testing.ts +1 -1
- package/src/util.ts +8 -0
- package/templates/config/license.config.json +4 -0
- package/templates/scaffold/app/backend/.env.example +1 -0
- package/templates/scaffold/app/backend/__dot__gitignore +4 -0
- package/templates/scaffold/app/backend/eslint.config.js +32 -0
- package/templates/scaffold/app/backend/package.json +31 -0
- package/templates/scaffold/app/backend/seed/data/AuditEvent.jsonl +0 -0
- package/templates/scaffold/app/backend/seed/data/Permission.jsonl +0 -0
- package/templates/scaffold/app/backend/seed/data/Permission.schema.ts +20 -0
- package/templates/scaffold/app/backend/seed/data/Role.jsonl +0 -0
- package/templates/scaffold/app/backend/seed/data/Role.schema.ts +20 -0
- package/templates/scaffold/app/backend/seed/data/RolePermission.jsonl +0 -0
- package/templates/scaffold/app/backend/seed/data/RolePermission.schema.ts +24 -0
- package/templates/scaffold/app/backend/seed/data/User.jsonl +1 -0
- package/templates/scaffold/app/backend/seed/data/User.schema.ts +20 -0
- package/templates/scaffold/app/backend/seed/data/UserRole.jsonl +0 -0
- package/templates/scaffold/app/backend/seed/data/UserRole.schema.ts +24 -0
- package/templates/scaffold/app/backend/seed/data/_User.jsonl +1 -0
- package/templates/scaffold/app/backend/seed/data/_User.schema.ts +30 -0
- package/templates/scaffold/app/backend/seed/exec.mjs +659 -0
- package/templates/scaffold/app/backend/src/executors/permissionCreated.ts +3 -0
- package/templates/scaffold/app/backend/src/executors/permissionDeleted.ts +3 -0
- package/templates/scaffold/app/backend/src/generated/kysely-tailordb.ts +83 -0
- package/templates/scaffold/app/backend/src/modules.ts +9 -0
- package/templates/scaffold/app/backend/src/resolvers/createUser.ts +46 -0
- package/templates/scaffold/app/backend/tailor.config.ts +68 -0
- package/templates/scaffold/app/backend/tailor.d.ts +15 -0
- package/templates/scaffold/app/backend/tsconfig.json +19 -0
- package/templates/scaffold/app/docs/actors/.gitkeep +0 -0
- package/templates/scaffold/app/docs/business-flow/.gitkeep +0 -0
- package/templates/scaffold/app/docs/resolver/.gitkeep +0 -0
- package/templates/scaffold/app/docs/screen/.gitkeep +0 -0
- package/templates/scaffold/app/frontend/.env.example +2 -0
- package/templates/scaffold/app/frontend/__dot__gitignore +3 -0
- package/templates/scaffold/app/frontend/components.json +23 -0
- package/templates/scaffold/app/frontend/eslint.config.js +48 -0
- package/templates/scaffold/app/frontend/index.html +13 -0
- package/templates/scaffold/app/frontend/package.json +53 -0
- package/templates/scaffold/app/frontend/scripts/generate-graphql.mjs +6 -0
- package/templates/scaffold/app/frontend/src/App.tsx +58 -0
- package/templates/scaffold/app/frontend/src/components/composed/empty-state.tsx +26 -0
- package/templates/scaffold/app/frontend/src/components/composed/error-fallback.tsx +28 -0
- package/templates/scaffold/app/frontend/src/components/composed/loading.tsx +13 -0
- package/templates/scaffold/app/frontend/src/components/ui/badge.tsx +39 -0
- package/templates/scaffold/app/frontend/src/components/ui/button.tsx +60 -0
- package/templates/scaffold/app/frontend/src/components/ui/card.tsx +75 -0
- package/templates/scaffold/app/frontend/src/components/ui/form.tsx +152 -0
- package/templates/scaffold/app/frontend/src/components/ui/input.tsx +21 -0
- package/templates/scaffold/app/frontend/src/components/ui/label.tsx +21 -0
- package/templates/scaffold/app/frontend/src/components/ui/spinner.tsx +16 -0
- package/templates/scaffold/app/frontend/src/components/ui/table.tsx +90 -0
- package/templates/scaffold/app/frontend/src/graphql/generated/graphql-env.d.ts +103 -0
- package/templates/scaffold/app/frontend/src/graphql/generated/schema.graphql +1235 -0
- package/templates/scaffold/app/frontend/src/graphql/index.ts +15 -0
- package/templates/scaffold/app/frontend/src/index.css +5 -0
- package/templates/scaffold/app/frontend/src/lib/auth-client.ts +17 -0
- package/templates/scaffold/app/frontend/src/lib/utils.ts +6 -0
- package/templates/scaffold/app/frontend/src/main.tsx +10 -0
- package/templates/scaffold/app/frontend/src/pages/page.tsx +20 -0
- package/templates/scaffold/app/frontend/src/pages/user-management/page.tsx +19 -0
- package/templates/scaffold/app/frontend/src/pages/user-management/profile/page.tsx +97 -0
- package/templates/scaffold/app/frontend/src/pages/user-management/user/[id]/components/user-detail.tsx +58 -0
- package/templates/scaffold/app/frontend/src/pages/user-management/user/[id]/page.tsx +51 -0
- package/templates/scaffold/app/frontend/src/pages/user-management/user/components/users-table.tsx +101 -0
- package/templates/scaffold/app/frontend/src/pages/user-management/user/create/components/create-user-form.tsx +99 -0
- package/templates/scaffold/app/frontend/src/pages/user-management/user/create/page.tsx +19 -0
- package/templates/scaffold/app/frontend/src/pages/user-management/user/page.tsx +61 -0
- package/templates/scaffold/app/frontend/src/providers/graphql-provider.tsx +21 -0
- package/templates/scaffold/app/frontend/tsconfig.app.json +35 -0
- package/templates/scaffold/app/frontend/tsconfig.json +16 -0
- package/templates/scaffold/app/frontend/tsconfig.node.json +23 -0
- package/templates/scaffold/app/frontend/vite.config.ts +23 -0
- package/templates/scaffold/module/command/.gitkeep +0 -0
- package/templates/scaffold/module/db/.gitkeep +0 -0
- package/templates/scaffold/module/executor/.gitkeep +0 -0
- package/templates/scaffold/module/generated/.gitkeep +0 -0
- package/templates/scaffold/module/index.ts +2 -0
- package/templates/scaffold/module/lib/errors.ts +1 -0
- package/templates/scaffold/module/lib/types.ts +4 -0
- package/templates/scaffold/module/module.ts +7 -0
- package/templates/scaffold/module/permissions.ts +3 -0
- package/templates/scaffold/module/query/.gitkeep +0 -0
- package/templates/scaffold/module/tailor.config.ts +13 -0
- package/templates/scaffold/module/testing/fixtures.ts +1 -0
- package/templates/workflows/erp-kit-check.yml +37 -0
- package/dist/cli.js +0 -1654
- package/skills/erp-kit-app-1-requirements/references/structure.md +0 -27
- package/skills/erp-kit-app-2-breakdown/SKILL.md +0 -95
- package/skills/erp-kit-app-2-breakdown/references/screen-detailview.md +0 -106
- package/skills/erp-kit-app-2-breakdown/references/screen-form.md +0 -139
- package/skills/erp-kit-app-2-breakdown/references/screen-listview.md +0 -153
- package/skills/erp-kit-app-2-breakdown/references/structure.md +0 -27
- package/skills/erp-kit-app-3-doc-review/SKILL.md +0 -116
- package/skills/erp-kit-app-3-doc-review/references/structure.md +0 -27
- package/skills/erp-kit-app-4-design/SKILL.md +0 -256
- package/skills/erp-kit-app-4-design/references/component.md +0 -50
- package/skills/erp-kit-app-4-design/references/screen-detailview.md +0 -106
- package/skills/erp-kit-app-4-design/references/screen-form.md +0 -139
- package/skills/erp-kit-app-4-design/references/screen-listview.md +0 -153
- package/skills/erp-kit-app-4-design/references/structure.md +0 -27
- package/skills/erp-kit-app-5-design-review/SKILL.md +0 -290
- package/skills/erp-kit-app-5-design-review/references/component.md +0 -50
- package/skills/erp-kit-app-5-design-review/references/screen-detailview.md +0 -106
- package/skills/erp-kit-app-5-design-review/references/screen-form.md +0 -139
- package/skills/erp-kit-app-5-design-review/references/screen-listview.md +0 -153
- package/skills/erp-kit-app-6-impl-spec/SKILL.md +0 -127
- package/skills/erp-kit-app-6-impl-spec/references/auth.md +0 -72
- package/skills/erp-kit-app-6-impl-spec/references/structure.md +0 -27
- package/skills/erp-kit-module-1-docs/SKILL.md +0 -111
- package/skills/erp-kit-module-2-feature-breakdown/SKILL.md +0 -76
- package/skills/erp-kit-module-3-doc-review/SKILL.md +0 -294
- package/skills/erp-kit-module-4-tdd/SKILL.md +0 -94
- package/skills/erp-kit-module-4-tdd/references/exports.md +0 -8
- package/skills/erp-kit-module-5-impl-review/SKILL.md +0 -410
- package/src/commands/scaffold-templates.ts +0 -65
- package/src/modules/shared/index.ts +0 -1
- package/src/modules/user-management/command/logAuditEvent.generated.ts +0 -6
- package/src/modules/user-management/command/logAuditEvent.test.ts +0 -187
- package/src/modules/user-management/command/logAuditEvent.ts +0 -56
- package/src/modules/user-management/db/auditEvent.ts +0 -47
- package/src/modules/user-management/docs/commands/LogAuditEvent.md +0 -37
- package/src/modules/user-management/docs/features/audit-trail.md +0 -80
- package/src/modules/user-management/docs/models/AuditEvent.md +0 -36
- /package/skills/{erp-kit-module-2-feature-breakdown → erp-kit-module-3-plan}/references/naming.md +0 -0
- /package/skills/{erp-kit-module-4-tdd → erp-kit-module-5-impl}/references/cross-module-dependency.md +0 -0
- /package/skills/{erp-kit-module-4-tdd → erp-kit-module-5-impl}/references/db-relations.md +0 -0
- /package/skills/{erp-kit-module-4-tdd → erp-kit-module-5-impl}/references/generated-code.md +0 -0
- /package/skills/{erp-kit-module-4-tdd → erp-kit-module-5-impl}/references/models.md +0 -0
- /package/skills/{erp-kit-module-5-impl-review → erp-kit-module-6-impl-review}/references/commands.md +0 -0
- /package/skills/{erp-kit-module-5-impl-review → erp-kit-module-6-impl-review}/references/testing.md +0 -0
- /package/src/modules/{product-management → audit}/.gitkeep +0 -0
- /package/src/{modules/shared → shared}/createContext.test.ts +0 -0
- /package/src/{modules/shared → shared}/createContext.ts +0 -0
- /package/src/{modules/shared → shared}/definePermissions.test.ts +0 -0
- /package/src/{modules/shared → shared}/definePermissions.ts +0 -0
- /package/src/{modules/shared → shared}/defineQuery.test.ts +0 -0
- /package/src/{modules/shared → shared}/defineQuery.ts +0 -0
- /package/src/{modules/shared → shared}/entityTypes.ts +0 -0
- /package/src/{modules/shared → shared}/errors.ts +0 -0
- /package/src/{modules/shared → shared}/requirePermission.test.ts +0 -0
- /package/src/{modules/shared → shared}/requirePermission.ts +0 -0
- /package/src/{modules/shared → shared}/result.ts +0 -0
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,1613 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { arg, defineCommand, runMain } from "politty";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import fs, { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
7
|
+
import path, { basename, dirname, join, relative, resolve } from "node:path";
|
|
8
|
+
import { execFile, execSync } from "node:child_process";
|
|
9
|
+
import fg from "fast-glob";
|
|
10
|
+
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
11
|
+
import { toString } from "mdast-util-to-string";
|
|
12
|
+
import { createServer, request } from "node:http";
|
|
13
|
+
import { createServer as createServer$1 } from "node:net";
|
|
14
|
+
import { readFile, readdir, stat } from "node:fs/promises";
|
|
15
|
+
//#region src/util.ts
|
|
16
|
+
const PACKAGE_ROOT = path.resolve(import.meta.dirname, "..");
|
|
17
|
+
function readErpKitVersion() {
|
|
18
|
+
return JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, "package.json"), "utf-8")).version;
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/commands/lib/distribute.ts
|
|
22
|
+
const SKILLS_SRC = path.join(PACKAGE_ROOT, "skills");
|
|
23
|
+
const WORKFLOWS_SRC = path.join(PACKAGE_ROOT, "templates", "workflows");
|
|
24
|
+
const CONFIG_SRC = path.join(PACKAGE_ROOT, "templates", "config");
|
|
25
|
+
function copyDirectoryRecursive(srcDir, destDir) {
|
|
26
|
+
let copied = 0;
|
|
27
|
+
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
28
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
29
|
+
const destPath = path.join(destDir, entry.name);
|
|
30
|
+
if (entry.isDirectory()) copied += copyDirectoryRecursive(srcPath, destPath);
|
|
31
|
+
else {
|
|
32
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
33
|
+
fs.copyFileSync(srcPath, destPath);
|
|
34
|
+
copied++;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return copied;
|
|
38
|
+
}
|
|
39
|
+
function discoverFrameworkSkills() {
|
|
40
|
+
if (!fs.existsSync(SKILLS_SRC)) return [];
|
|
41
|
+
return fs.readdirSync(SKILLS_SRC, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.startsWith("erp-kit-")).map((entry) => entry.name);
|
|
42
|
+
}
|
|
43
|
+
function copySkills(cwd) {
|
|
44
|
+
const skillsDest = path.join(cwd, ".agents", "skills");
|
|
45
|
+
let removed = 0;
|
|
46
|
+
if (fs.existsSync(skillsDest)) {
|
|
47
|
+
for (const entry of fs.readdirSync(skillsDest)) if (entry.startsWith("erp-kit-")) {
|
|
48
|
+
fs.rmSync(path.join(skillsDest, entry), {
|
|
49
|
+
recursive: true,
|
|
50
|
+
force: true
|
|
51
|
+
});
|
|
52
|
+
removed++;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
let copied = 0;
|
|
56
|
+
for (const skill of discoverFrameworkSkills()) {
|
|
57
|
+
const srcSkillDir = path.join(SKILLS_SRC, skill);
|
|
58
|
+
if (!fs.existsSync(srcSkillDir)) continue;
|
|
59
|
+
const destDir = path.join(skillsDest, skill);
|
|
60
|
+
copied += copyDirectoryRecursive(srcSkillDir, destDir);
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
copied,
|
|
64
|
+
removed
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function copyWorkflows(cwd) {
|
|
68
|
+
const workflowsDest = path.join(cwd, ".github", "workflows");
|
|
69
|
+
if (!fs.existsSync(WORKFLOWS_SRC)) return {
|
|
70
|
+
copied: 0,
|
|
71
|
+
removed: 0
|
|
72
|
+
};
|
|
73
|
+
const workflowFiles = fs.readdirSync(WORKFLOWS_SRC).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
|
|
74
|
+
let copied = 0;
|
|
75
|
+
for (const file of workflowFiles) {
|
|
76
|
+
fs.mkdirSync(workflowsDest, { recursive: true });
|
|
77
|
+
fs.copyFileSync(path.join(WORKFLOWS_SRC, file), path.join(workflowsDest, file));
|
|
78
|
+
copied++;
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
copied,
|
|
82
|
+
removed: 0
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function copyConfigs(cwd) {
|
|
86
|
+
if (!fs.existsSync(CONFIG_SRC)) return {
|
|
87
|
+
copied: 0,
|
|
88
|
+
removed: 0
|
|
89
|
+
};
|
|
90
|
+
let copied = 0;
|
|
91
|
+
for (const entry of fs.readdirSync(CONFIG_SRC)) {
|
|
92
|
+
const destPath = path.join(cwd, entry);
|
|
93
|
+
fs.copyFileSync(path.join(CONFIG_SRC, entry), destPath);
|
|
94
|
+
copied++;
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
copied,
|
|
98
|
+
removed: 0
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function setupSymlink(cwd) {
|
|
102
|
+
const skillsDest = path.join(cwd, ".agents", "skills");
|
|
103
|
+
const claudeSkills = path.join(cwd, ".claude", "skills");
|
|
104
|
+
const relTarget = path.relative(path.dirname(claudeSkills), skillsDest);
|
|
105
|
+
let exists = false;
|
|
106
|
+
try {
|
|
107
|
+
fs.lstatSync(claudeSkills);
|
|
108
|
+
exists = true;
|
|
109
|
+
} catch {}
|
|
110
|
+
if (exists) {
|
|
111
|
+
if (fs.lstatSync(claudeSkills).isSymbolicLink()) {
|
|
112
|
+
if (fs.readlinkSync(claudeSkills) === relTarget) return "already_linked";
|
|
113
|
+
fs.unlinkSync(claudeSkills);
|
|
114
|
+
fs.symlinkSync(relTarget, claudeSkills);
|
|
115
|
+
return "linked";
|
|
116
|
+
}
|
|
117
|
+
return "skipped_directory";
|
|
118
|
+
}
|
|
119
|
+
fs.mkdirSync(path.dirname(claudeSkills), { recursive: true });
|
|
120
|
+
fs.symlinkSync(relTarget, claudeSkills);
|
|
121
|
+
return "linked";
|
|
122
|
+
}
|
|
123
|
+
function isAlreadyInitialized(cwd) {
|
|
124
|
+
const skillsDest = path.join(cwd, ".agents", "skills");
|
|
125
|
+
if (!fs.existsSync(skillsDest)) return false;
|
|
126
|
+
return fs.readdirSync(skillsDest).some((entry) => entry.startsWith("erp-kit-"));
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/commands/init.ts
|
|
130
|
+
function runInit(cwd) {
|
|
131
|
+
console.log(chalk.bold("erp-kit init\n"));
|
|
132
|
+
if (isAlreadyInitialized(cwd)) {
|
|
133
|
+
console.log(chalk.yellow("Already initialized. Use `erp-kit update` to refresh framework resources."));
|
|
134
|
+
return 1;
|
|
135
|
+
}
|
|
136
|
+
const skills = copySkills(cwd);
|
|
137
|
+
console.log(chalk.green(` Copied ${skills.copied} skill files to .agents/skills/`));
|
|
138
|
+
const symlink = setupSymlink(cwd);
|
|
139
|
+
if (symlink === "linked") console.log(chalk.green(" .claude/skills -> .agents/skills/ (linked)"));
|
|
140
|
+
else if (symlink === "skipped_directory") console.log(chalk.yellow(" Skipped .claude/skills (directory exists, not a symlink)"));
|
|
141
|
+
const workflows = copyWorkflows(cwd);
|
|
142
|
+
if (workflows.copied > 0) console.log(chalk.green(` Copied ${workflows.copied} workflow(s) to .github/workflows/`));
|
|
143
|
+
const configs = copyConfigs(cwd);
|
|
144
|
+
if (configs.copied > 0) console.log(chalk.green(` Copied ${configs.copied} config file(s)`));
|
|
145
|
+
console.log(chalk.bold.green("\nDone! Run `erp-kit check` to validate your docs."));
|
|
146
|
+
return 0;
|
|
147
|
+
}
|
|
148
|
+
//#endregion
|
|
149
|
+
//#region src/commands/update.ts
|
|
150
|
+
const VALID_RESOURCES = ["skills", "workflows"];
|
|
151
|
+
function runUpdate(cwd, resources) {
|
|
152
|
+
console.log(chalk.bold("erp-kit update\n"));
|
|
153
|
+
const selected = resources.length === 0 ? new Set(VALID_RESOURCES) : new Set(resources.filter((r) => VALID_RESOURCES.includes(r)));
|
|
154
|
+
if (selected.size === 0) {
|
|
155
|
+
console.error(chalk.red(`Invalid resource. Valid: ${VALID_RESOURCES.join(", ")}`));
|
|
156
|
+
return 1;
|
|
157
|
+
}
|
|
158
|
+
if (selected.has("skills")) {
|
|
159
|
+
const result = copySkills(cwd);
|
|
160
|
+
if (result.removed > 0) console.log(chalk.green(` Removed ${result.removed} stale erp-kit-* skills`));
|
|
161
|
+
console.log(chalk.green(` Copied ${result.copied} skill files to .agents/skills/`));
|
|
162
|
+
}
|
|
163
|
+
if (selected.has("workflows")) {
|
|
164
|
+
const result = copyWorkflows(cwd);
|
|
165
|
+
if (result.copied > 0) console.log(chalk.green(` Copied ${result.copied} workflow(s) to .github/workflows/`));
|
|
166
|
+
}
|
|
167
|
+
console.log(chalk.bold.green("\nDone!"));
|
|
168
|
+
return 0;
|
|
169
|
+
}
|
|
170
|
+
//#endregion
|
|
171
|
+
//#region src/commands/license.ts
|
|
172
|
+
const licenseGroups = {
|
|
173
|
+
reciprocal: [
|
|
174
|
+
"APSL-1.0",
|
|
175
|
+
"APSL-1.1",
|
|
176
|
+
"APSL-1.2",
|
|
177
|
+
"APSL-2.0",
|
|
178
|
+
"CDDL-1.0",
|
|
179
|
+
"CDDL-1.1",
|
|
180
|
+
"CPL-1.0",
|
|
181
|
+
"EPL-1.0",
|
|
182
|
+
"EPL-2.0",
|
|
183
|
+
"FreeImage",
|
|
184
|
+
"IPL-1.0",
|
|
185
|
+
"MPL-1.0",
|
|
186
|
+
"MPL-1.1",
|
|
187
|
+
"MPL-2.0",
|
|
188
|
+
"Ruby"
|
|
189
|
+
],
|
|
190
|
+
notice: [
|
|
191
|
+
"AFL-1.1",
|
|
192
|
+
"AFL-1.2",
|
|
193
|
+
"AFL-2.0",
|
|
194
|
+
"AFL-2.1",
|
|
195
|
+
"AFL-3.0",
|
|
196
|
+
"Apache-1.0",
|
|
197
|
+
"Apache-1.1",
|
|
198
|
+
"Apache-2.0",
|
|
199
|
+
"Artistic-1.0-cl8",
|
|
200
|
+
"Artistic-1.0-Perl",
|
|
201
|
+
"Artistic-1.0",
|
|
202
|
+
"Artistic-2.0",
|
|
203
|
+
"BSL-1.0",
|
|
204
|
+
"BSD-2-Clause-FreeBSD",
|
|
205
|
+
"BSD-2-Clause-NetBSD",
|
|
206
|
+
"BSD-2-Clause",
|
|
207
|
+
"BSD-3-Clause-Attribution",
|
|
208
|
+
"BSD-3-Clause-Clear",
|
|
209
|
+
"BSD-3-Clause-LBNL",
|
|
210
|
+
"BSD-3-Clause",
|
|
211
|
+
"BSD-4-Clause",
|
|
212
|
+
"BSD-4-Clause-UC",
|
|
213
|
+
"BSD-Protection",
|
|
214
|
+
"CC-BY-1.0",
|
|
215
|
+
"CC-BY-2.0",
|
|
216
|
+
"CC-BY-2.5",
|
|
217
|
+
"CC-BY-3.0",
|
|
218
|
+
"CC-BY-4.0",
|
|
219
|
+
"FTL",
|
|
220
|
+
"ISC",
|
|
221
|
+
"ImageMagick",
|
|
222
|
+
"Libpng",
|
|
223
|
+
"Lil-1.0",
|
|
224
|
+
"Linux-OpenIB",
|
|
225
|
+
"LPL-1.02",
|
|
226
|
+
"LPL-1.0",
|
|
227
|
+
"MS-PL",
|
|
228
|
+
"MIT",
|
|
229
|
+
"NCSA",
|
|
230
|
+
"OpenSSL",
|
|
231
|
+
"PHP-3.01",
|
|
232
|
+
"PHP-3.0",
|
|
233
|
+
"PIL",
|
|
234
|
+
"Python-2.0",
|
|
235
|
+
"Python-2.0-complete",
|
|
236
|
+
"PostgreSQL",
|
|
237
|
+
"SGI-B-1.0",
|
|
238
|
+
"SGI-B-1.1",
|
|
239
|
+
"SGI-B-2.0",
|
|
240
|
+
"Unicode-DFS-2015",
|
|
241
|
+
"Unicode-DFS-2016",
|
|
242
|
+
"Unicode-TOU",
|
|
243
|
+
"UPL-1.0",
|
|
244
|
+
"W3C-19980720",
|
|
245
|
+
"W3C-20150513",
|
|
246
|
+
"W3C",
|
|
247
|
+
"X11",
|
|
248
|
+
"Xnet",
|
|
249
|
+
"Zend-2.0",
|
|
250
|
+
"zlib-acknowledgement",
|
|
251
|
+
"Zlib",
|
|
252
|
+
"ZPL-1.1",
|
|
253
|
+
"ZPL-2.0",
|
|
254
|
+
"ZPL-2.1"
|
|
255
|
+
],
|
|
256
|
+
unencumbered: [
|
|
257
|
+
"CC0-1.0",
|
|
258
|
+
"Unlicense",
|
|
259
|
+
"0BSD"
|
|
260
|
+
]
|
|
261
|
+
};
|
|
262
|
+
function buildAllowSet(config) {
|
|
263
|
+
const set = /* @__PURE__ */ new Set();
|
|
264
|
+
for (const group of config.groups) for (const license of licenseGroups[group]) set.add(license);
|
|
265
|
+
if (config.allow) for (const license of config.allow) set.add(license);
|
|
266
|
+
if (config.deny) for (const license of config.deny) set.delete(license);
|
|
267
|
+
return set;
|
|
268
|
+
}
|
|
269
|
+
function isLicenseAllowed(licenseString, allowSet) {
|
|
270
|
+
if (/\s+(?:OR|AND)\s+/i.test(licenseString)) return licenseString.replace(/[()]/g, "").trim().split(/\s+(?:OR|AND)\s+/i).map((l) => l.trim()).filter((l) => l.length > 0).every((l) => allowSet.has(l));
|
|
271
|
+
return allowSet.has(licenseString);
|
|
272
|
+
}
|
|
273
|
+
function runLicenseList() {
|
|
274
|
+
for (const [group, licenses] of Object.entries(licenseGroups)) {
|
|
275
|
+
console.log(`${group} (${licenses.length} licenses):`);
|
|
276
|
+
for (const license of licenses) console.log(` ${license}`);
|
|
277
|
+
console.log();
|
|
278
|
+
}
|
|
279
|
+
return 0;
|
|
280
|
+
}
|
|
281
|
+
function runLicenseCheck(configPath) {
|
|
282
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
283
|
+
const config = JSON.parse(raw);
|
|
284
|
+
const validGroups = Object.keys(licenseGroups);
|
|
285
|
+
for (const group of config.groups) if (!validGroups.includes(group)) {
|
|
286
|
+
console.error(`Unknown license group: "${group}". Valid groups: ${validGroups.join(", ")}`);
|
|
287
|
+
return 2;
|
|
288
|
+
}
|
|
289
|
+
const allowSet = buildAllowSet(config);
|
|
290
|
+
console.log("Checking licenses...\n");
|
|
291
|
+
execSync("pnpm licenses list", { stdio: "inherit" });
|
|
292
|
+
const output = execSync("pnpm licenses list --json");
|
|
293
|
+
const licensesJson = JSON.parse(output.toString());
|
|
294
|
+
const violations = [];
|
|
295
|
+
for (const [license, packages] of Object.entries(licensesJson)) if (!isLicenseAllowed(license, allowSet)) for (const pkg of packages) violations.push({
|
|
296
|
+
package: pkg.name,
|
|
297
|
+
license
|
|
298
|
+
});
|
|
299
|
+
if (violations.length === 0) {
|
|
300
|
+
console.log("All licenses are allowed.");
|
|
301
|
+
return 0;
|
|
302
|
+
}
|
|
303
|
+
console.error("Found dependencies with disallowed licenses:\n");
|
|
304
|
+
for (const violation of violations) {
|
|
305
|
+
console.error(` - ${violation.package}`);
|
|
306
|
+
console.error(` License: ${violation.license}\n`);
|
|
307
|
+
}
|
|
308
|
+
return 1;
|
|
309
|
+
}
|
|
310
|
+
//#endregion
|
|
311
|
+
//#region src/mdschema.ts
|
|
312
|
+
const require = createRequire(import.meta.url);
|
|
313
|
+
function getMdschemaBin() {
|
|
314
|
+
const pkgPath = require.resolve("@jackchuka/mdschema/package.json");
|
|
315
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
316
|
+
const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.mdschema;
|
|
317
|
+
if (!bin) throw new Error("Could not resolve mdschema binary from package.json bin field");
|
|
318
|
+
return path.join(path.dirname(pkgPath), bin);
|
|
319
|
+
}
|
|
320
|
+
function runMdschema(args, cwd) {
|
|
321
|
+
return new Promise((resolve) => {
|
|
322
|
+
execFile(getMdschemaBin(), args, {
|
|
323
|
+
encoding: "utf-8",
|
|
324
|
+
cwd,
|
|
325
|
+
timeout: 3e4
|
|
326
|
+
}, (error, stdout, stderr) => {
|
|
327
|
+
if (error) {
|
|
328
|
+
const execError = error;
|
|
329
|
+
resolve({
|
|
330
|
+
exitCode: execError.code === "ENOENT" ? 127 : execError.status ?? 1,
|
|
331
|
+
stdout: stdout ?? "",
|
|
332
|
+
stderr: stderr ?? ""
|
|
333
|
+
});
|
|
334
|
+
} else resolve({
|
|
335
|
+
exitCode: 0,
|
|
336
|
+
stdout: stdout ?? "",
|
|
337
|
+
stderr: stderr ?? ""
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
//#endregion
|
|
343
|
+
//#region src/schemas.ts
|
|
344
|
+
const SCHEMAS_ROOT = path.join(PACKAGE_ROOT, "schemas");
|
|
345
|
+
const MODULE_SCHEMAS = {
|
|
346
|
+
module: path.join(SCHEMAS_ROOT, "module", "module.yml"),
|
|
347
|
+
command: path.join(SCHEMAS_ROOT, "module", "command.yml"),
|
|
348
|
+
model: path.join(SCHEMAS_ROOT, "module", "model.yml"),
|
|
349
|
+
feature: path.join(SCHEMAS_ROOT, "module", "feature.yml"),
|
|
350
|
+
query: path.join(SCHEMAS_ROOT, "module", "query.yml")
|
|
351
|
+
};
|
|
352
|
+
const APP_COMPOSE_SCHEMAS = {
|
|
353
|
+
app: path.join(SCHEMAS_ROOT, "app-compose", "requirements.yml"),
|
|
354
|
+
actors: path.join(SCHEMAS_ROOT, "app-compose", "actors.yml"),
|
|
355
|
+
"business-flow": path.join(SCHEMAS_ROOT, "app-compose", "business-flow.yml"),
|
|
356
|
+
story: path.join(SCHEMAS_ROOT, "app-compose", "story.yml"),
|
|
357
|
+
screen: path.join(SCHEMAS_ROOT, "app-compose", "screen.yml"),
|
|
358
|
+
resolver: path.join(SCHEMAS_ROOT, "app-compose", "resolver.yml")
|
|
359
|
+
};
|
|
360
|
+
const ALL_SCHEMAS = {
|
|
361
|
+
...MODULE_SCHEMAS,
|
|
362
|
+
...APP_COMPOSE_SCHEMAS
|
|
363
|
+
};
|
|
364
|
+
//#endregion
|
|
365
|
+
//#region src/commands/check.ts
|
|
366
|
+
function buildCheckTargets(config) {
|
|
367
|
+
const targets = [];
|
|
368
|
+
if (config.modulesRoot) {
|
|
369
|
+
const m = config.modulesRoot;
|
|
370
|
+
targets.push({
|
|
371
|
+
glob: `${m}/[a-zA-Z]*/docs/features/*.md`,
|
|
372
|
+
schemaKey: "feature"
|
|
373
|
+
}, {
|
|
374
|
+
glob: `${m}/[a-zA-Z]*/docs/commands/*.md`,
|
|
375
|
+
schemaKey: "command"
|
|
376
|
+
}, {
|
|
377
|
+
glob: `${m}/[a-zA-Z]*/docs/models/*.md`,
|
|
378
|
+
schemaKey: "model"
|
|
379
|
+
}, {
|
|
380
|
+
glob: `${m}/[a-zA-Z]*/docs/queries/*.md`,
|
|
381
|
+
schemaKey: "query"
|
|
382
|
+
}, {
|
|
383
|
+
glob: `${m}/[a-zA-Z]*/README.md`,
|
|
384
|
+
schemaKey: "module"
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
if (config.appRoot) {
|
|
388
|
+
const a = config.appRoot;
|
|
389
|
+
targets.push({
|
|
390
|
+
glob: `${a}/[a-zA-Z]*/README.md`,
|
|
391
|
+
schemaKey: "app"
|
|
392
|
+
}, {
|
|
393
|
+
glob: `${a}/[a-zA-Z]*/docs/actors/*.md`,
|
|
394
|
+
schemaKey: "actors"
|
|
395
|
+
}, {
|
|
396
|
+
glob: `${a}/[a-zA-Z]*/docs/business-flow/*/README.md`,
|
|
397
|
+
schemaKey: "business-flow"
|
|
398
|
+
}, {
|
|
399
|
+
glob: `${a}/[a-zA-Z]*/docs/business-flow/*/story/*.md`,
|
|
400
|
+
schemaKey: "story"
|
|
401
|
+
}, {
|
|
402
|
+
glob: `${a}/[a-zA-Z]*/docs/screen/*.md`,
|
|
403
|
+
schemaKey: "screen"
|
|
404
|
+
}, {
|
|
405
|
+
glob: `${a}/[a-zA-Z]*/docs/resolver/*.md`,
|
|
406
|
+
schemaKey: "resolver"
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
return targets;
|
|
410
|
+
}
|
|
411
|
+
async function runCheck(config, cwd) {
|
|
412
|
+
const targets = buildCheckTargets(config);
|
|
413
|
+
const results = await Promise.all(targets.map(async (target) => {
|
|
414
|
+
const schemaPath = ALL_SCHEMAS[target.schemaKey];
|
|
415
|
+
if (!schemaPath) {
|
|
416
|
+
console.error(`Unknown schema key: ${target.schemaKey}`);
|
|
417
|
+
return 2;
|
|
418
|
+
}
|
|
419
|
+
const { exitCode, stdout, stderr } = await runMdschema([
|
|
420
|
+
"check",
|
|
421
|
+
target.glob,
|
|
422
|
+
"--schema",
|
|
423
|
+
schemaPath
|
|
424
|
+
], cwd);
|
|
425
|
+
if (stdout.trim()) console.log(stdout);
|
|
426
|
+
if (stderr.trim()) console.error(stderr);
|
|
427
|
+
return exitCode;
|
|
428
|
+
}));
|
|
429
|
+
if (results.includes(2)) return 2;
|
|
430
|
+
return results.some((code) => code !== 0) ? 1 : 0;
|
|
431
|
+
}
|
|
432
|
+
//#endregion
|
|
433
|
+
//#region src/commands/parse-doc-test-cases.ts
|
|
434
|
+
function isHeading$1(node) {
|
|
435
|
+
return node.type === "heading";
|
|
436
|
+
}
|
|
437
|
+
function isList$1(node) {
|
|
438
|
+
return node.type === "list";
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Parse test case descriptions from the `## Test Cases` section of a Markdown doc.
|
|
442
|
+
* Uses mdast to traverse the AST instead of string matching.
|
|
443
|
+
*/
|
|
444
|
+
function parseTestCasesFromDoc(markdown) {
|
|
445
|
+
const tree = fromMarkdown(markdown);
|
|
446
|
+
const cases = [];
|
|
447
|
+
let collecting = false;
|
|
448
|
+
for (const node of tree.children) {
|
|
449
|
+
if (isHeading$1(node)) {
|
|
450
|
+
if (collecting) break;
|
|
451
|
+
if (node.depth === 2 && toString(node) === "Test Cases") {
|
|
452
|
+
collecting = true;
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if (collecting && isList$1(node)) for (const item of node.children) {
|
|
457
|
+
const text = toString(item).trim();
|
|
458
|
+
if (text) cases.push(text);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return cases;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Parse `it("...")` descriptions from a test file using regex.
|
|
465
|
+
* Test files are not Markdown, so regex is appropriate here.
|
|
466
|
+
*/
|
|
467
|
+
function parseItDescriptionsFromTest(content) {
|
|
468
|
+
const descriptions = [];
|
|
469
|
+
const regex = /\bit\(\s*["'`]([^"'`]+)["'`]/g;
|
|
470
|
+
let match;
|
|
471
|
+
while ((match = regex.exec(content)) !== null) descriptions.push(match[1]);
|
|
472
|
+
return descriptions;
|
|
473
|
+
}
|
|
474
|
+
//#endregion
|
|
475
|
+
//#region src/commands/sync-check.ts
|
|
476
|
+
function moduleCategories(root) {
|
|
477
|
+
return [
|
|
478
|
+
{
|
|
479
|
+
name: "command",
|
|
480
|
+
sourcePattern: `${root}/*/command/*.ts`,
|
|
481
|
+
docPattern: `${root}/*/docs/commands/*.md`,
|
|
482
|
+
exclusions: [/\.test\.ts$/, /\.generated\.ts$/]
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
name: "model",
|
|
486
|
+
sourcePattern: `${root}/*/db/*.ts`,
|
|
487
|
+
docPattern: `${root}/*/docs/models/*.md`,
|
|
488
|
+
exclusions: [/\.test\.ts$/, /^index\.ts$/]
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
name: "query",
|
|
492
|
+
sourcePattern: `${root}/*/query/*.ts`,
|
|
493
|
+
docPattern: `${root}/*/docs/queries/*.md`,
|
|
494
|
+
exclusions: [/\.test\.ts$/]
|
|
495
|
+
}
|
|
496
|
+
];
|
|
497
|
+
}
|
|
498
|
+
function appComposeCategories(root) {
|
|
499
|
+
return [{
|
|
500
|
+
name: "resolver",
|
|
501
|
+
sourcePattern: `${root}/*/backend/src/modules/**/resolvers/*.ts`,
|
|
502
|
+
docPattern: `${root}/*/docs/resolver/*.md`,
|
|
503
|
+
exclusions: [/\.test\.ts$/, /^index\.ts$/]
|
|
504
|
+
}];
|
|
505
|
+
}
|
|
506
|
+
function shouldExclude(fileName, exclusions) {
|
|
507
|
+
return exclusions.some((pattern) => pattern.test(fileName));
|
|
508
|
+
}
|
|
509
|
+
async function runSyncCheck(config, cwd) {
|
|
510
|
+
const errors = [];
|
|
511
|
+
let totalSources = 0;
|
|
512
|
+
let totalDocs = 0;
|
|
513
|
+
const allCategories = [];
|
|
514
|
+
if (config.modulesRoot) allCategories.push(...moduleCategories(config.modulesRoot));
|
|
515
|
+
if (config.appRoot) allCategories.push(...appComposeCategories(config.appRoot));
|
|
516
|
+
for (const category of allCategories) {
|
|
517
|
+
const sources = await fg(category.sourcePattern, { cwd });
|
|
518
|
+
const docs = await fg(category.docPattern, { cwd });
|
|
519
|
+
const sourceBasenames = /* @__PURE__ */ new Map();
|
|
520
|
+
const docBasenames = /* @__PURE__ */ new Map();
|
|
521
|
+
for (const sourcePath of sources) {
|
|
522
|
+
if (shouldExclude(path.basename(sourcePath), category.exclusions)) continue;
|
|
523
|
+
let basename = path.basename(sourcePath, path.extname(sourcePath));
|
|
524
|
+
if (basename.endsWith(".generated")) basename = basename.slice(0, -10);
|
|
525
|
+
sourceBasenames.set(basename.toLowerCase(), sourcePath);
|
|
526
|
+
}
|
|
527
|
+
for (const docPath of docs) {
|
|
528
|
+
const basename = path.basename(docPath, path.extname(docPath));
|
|
529
|
+
docBasenames.set(basename.toLowerCase(), docPath);
|
|
530
|
+
}
|
|
531
|
+
for (const [basename, sourcePath] of sourceBasenames) if (!docBasenames.has(basename)) errors.push({
|
|
532
|
+
type: "missing-doc",
|
|
533
|
+
category: category.name,
|
|
534
|
+
sourcePath,
|
|
535
|
+
expectedBasename: basename
|
|
536
|
+
});
|
|
537
|
+
for (const [basename, docPath] of docBasenames) if (!sourceBasenames.has(basename)) errors.push({
|
|
538
|
+
type: "orphaned-doc",
|
|
539
|
+
category: category.name,
|
|
540
|
+
docPath,
|
|
541
|
+
expectedBasename: basename
|
|
542
|
+
});
|
|
543
|
+
totalSources += sourceBasenames.size;
|
|
544
|
+
totalDocs += docBasenames.size;
|
|
545
|
+
}
|
|
546
|
+
if (config.modulesRoot) {
|
|
547
|
+
const testCaseErrors = await runTestCaseSyncCheck(config.modulesRoot, cwd);
|
|
548
|
+
errors.push(...testCaseErrors);
|
|
549
|
+
}
|
|
550
|
+
return {
|
|
551
|
+
exitCode: errors.length > 0 ? 1 : 0,
|
|
552
|
+
errors,
|
|
553
|
+
summary: {
|
|
554
|
+
categoriesChecked: allCategories.length,
|
|
555
|
+
totalSources,
|
|
556
|
+
totalDocs
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
function testCaseCategories(root) {
|
|
561
|
+
return [{
|
|
562
|
+
name: "command-test-case",
|
|
563
|
+
docPattern: `${root}/*/docs/commands/*.md`,
|
|
564
|
+
testDir: "command"
|
|
565
|
+
}, {
|
|
566
|
+
name: "query-test-case",
|
|
567
|
+
docPattern: `${root}/*/docs/queries/*.md`,
|
|
568
|
+
testDir: "query"
|
|
569
|
+
}];
|
|
570
|
+
}
|
|
571
|
+
function toCamelCase(pascalCase) {
|
|
572
|
+
return pascalCase.charAt(0).toLowerCase() + pascalCase.slice(1);
|
|
573
|
+
}
|
|
574
|
+
async function runTestCaseSyncCheck(root, cwd) {
|
|
575
|
+
const errors = [];
|
|
576
|
+
const categories = testCaseCategories(root);
|
|
577
|
+
for (const category of categories) {
|
|
578
|
+
const docPaths = await fg(category.docPattern, { cwd });
|
|
579
|
+
for (const docPath of docPaths) {
|
|
580
|
+
const docFullPath = path.join(cwd, docPath);
|
|
581
|
+
const docTestCases = parseTestCasesFromDoc(fs.readFileSync(docFullPath, "utf-8"));
|
|
582
|
+
if (docTestCases.length === 0) continue;
|
|
583
|
+
const docBasename = path.basename(docPath, ".md");
|
|
584
|
+
const docsIndex = docPath.indexOf("/docs/");
|
|
585
|
+
if (docsIndex === -1) continue;
|
|
586
|
+
const modulePath = docPath.substring(0, docsIndex);
|
|
587
|
+
const testFileName = `${toCamelCase(docBasename)}.test.ts`;
|
|
588
|
+
const testPath = path.join(modulePath, category.testDir, testFileName);
|
|
589
|
+
const testFullPath = path.join(cwd, testPath);
|
|
590
|
+
if (!fs.existsSync(testFullPath)) continue;
|
|
591
|
+
const itDescriptions = parseItDescriptionsFromTest(fs.readFileSync(testFullPath, "utf-8"));
|
|
592
|
+
const docSet = new Set(docTestCases);
|
|
593
|
+
const testSet = new Set(itDescriptions);
|
|
594
|
+
for (const docCase of docSet) if (!testSet.has(docCase)) errors.push({
|
|
595
|
+
type: "missing-test-case",
|
|
596
|
+
category: category.name,
|
|
597
|
+
docPath,
|
|
598
|
+
sourcePath: testPath,
|
|
599
|
+
expectedBasename: docCase
|
|
600
|
+
});
|
|
601
|
+
for (const testCase of testSet) if (!docSet.has(testCase)) errors.push({
|
|
602
|
+
type: "extra-test-case",
|
|
603
|
+
category: category.name,
|
|
604
|
+
docPath,
|
|
605
|
+
sourcePath: testPath,
|
|
606
|
+
expectedBasename: testCase
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
return errors;
|
|
611
|
+
}
|
|
612
|
+
function formatSyncCheckReport(result) {
|
|
613
|
+
const lines = [];
|
|
614
|
+
lines.push(chalk.bold("docs-sync-check: Checking source-documentation correspondence...\n"));
|
|
615
|
+
if (result.errors.length > 0) {
|
|
616
|
+
lines.push(chalk.red.bold("Errors:\n"));
|
|
617
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
618
|
+
for (const error of result.errors) {
|
|
619
|
+
const existing = byCategory.get(error.category) ?? [];
|
|
620
|
+
existing.push(error);
|
|
621
|
+
byCategory.set(error.category, existing);
|
|
622
|
+
}
|
|
623
|
+
for (const [category, categoryErrors] of byCategory) {
|
|
624
|
+
lines.push(chalk.cyan(` Category: ${category}\n`));
|
|
625
|
+
for (const error of categoryErrors) {
|
|
626
|
+
if (error.type === "missing-doc") {
|
|
627
|
+
lines.push(` ${chalk.red(error.sourcePath)}`);
|
|
628
|
+
lines.push(` ${chalk.yellow("Missing documentation for:")} ${error.expectedBasename}`);
|
|
629
|
+
} else if (error.type === "orphaned-doc") {
|
|
630
|
+
lines.push(` ${chalk.red(error.docPath)}`);
|
|
631
|
+
lines.push(` ${chalk.yellow("Orphaned documentation:")} no source file for ${error.expectedBasename}`);
|
|
632
|
+
} else if (error.type === "missing-test-case") {
|
|
633
|
+
lines.push(` ${chalk.red(error.sourcePath)}`);
|
|
634
|
+
lines.push(` ${chalk.yellow("Missing test case:")} "${error.expectedBasename}" is in doc but not in test`);
|
|
635
|
+
} else if (error.type === "extra-test-case") {
|
|
636
|
+
lines.push(` ${chalk.red(error.sourcePath)}`);
|
|
637
|
+
lines.push(` ${chalk.yellow("Extra test case:")} "${error.expectedBasename}" is in test but not in doc`);
|
|
638
|
+
}
|
|
639
|
+
lines.push("");
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
} else lines.push(chalk.green("All source files have corresponding documentation.\n"));
|
|
643
|
+
lines.push(chalk.bold("Summary:"));
|
|
644
|
+
lines.push(` Categories checked: ${result.summary.categoriesChecked}`);
|
|
645
|
+
lines.push(` Source files: ${result.summary.totalSources}, Doc files: ${result.summary.totalDocs}`);
|
|
646
|
+
if (result.errors.length > 0) {
|
|
647
|
+
lines.push(chalk.red(` Errors: ${result.errors.length}`));
|
|
648
|
+
lines.push("");
|
|
649
|
+
lines.push(chalk.red.bold(`docs-sync-check failed with ${result.errors.length} error(s).`));
|
|
650
|
+
} else {
|
|
651
|
+
lines.push(chalk.green(" Errors: 0"));
|
|
652
|
+
lines.push("");
|
|
653
|
+
lines.push(chalk.green.bold("docs-sync-check passed."));
|
|
654
|
+
}
|
|
655
|
+
return lines.join("\n");
|
|
656
|
+
}
|
|
657
|
+
//#endregion
|
|
658
|
+
//#region src/commands/scaffold.ts
|
|
659
|
+
const MODULE_TYPES = [
|
|
660
|
+
"module",
|
|
661
|
+
"feature",
|
|
662
|
+
"command",
|
|
663
|
+
"model",
|
|
664
|
+
"query"
|
|
665
|
+
];
|
|
666
|
+
const APP_TYPES = [
|
|
667
|
+
"app",
|
|
668
|
+
"actors",
|
|
669
|
+
"business-flow",
|
|
670
|
+
"story",
|
|
671
|
+
"screen",
|
|
672
|
+
"resolver"
|
|
673
|
+
];
|
|
674
|
+
[...MODULE_TYPES, ...APP_TYPES];
|
|
675
|
+
const MODULE_DIR_MAP = {
|
|
676
|
+
feature: "docs/features",
|
|
677
|
+
command: "docs/commands",
|
|
678
|
+
model: "docs/models",
|
|
679
|
+
query: "docs/queries"
|
|
680
|
+
};
|
|
681
|
+
const APP_DIR_MAP = {
|
|
682
|
+
actors: "docs/actors",
|
|
683
|
+
"business-flow": "docs/business-flow",
|
|
684
|
+
screen: "docs/screen",
|
|
685
|
+
resolver: "docs/resolver"
|
|
686
|
+
};
|
|
687
|
+
function resolveScaffoldPath(type, parentName, name, root) {
|
|
688
|
+
if (type === "module" || type === "app") return path.join(root, parentName, "README.md");
|
|
689
|
+
if (!name) throw new Error(`Name is required for scaffold type "${type}"`);
|
|
690
|
+
if (type === "business-flow") return path.join(root, parentName, "docs/business-flow", name, "README.md");
|
|
691
|
+
if (type === "story") {
|
|
692
|
+
const parts = name.split("/");
|
|
693
|
+
if (parts.length !== 2) throw new Error(`Story name must be "<flow>/<story>" (e.g., "onboarding/admin--create-user")`);
|
|
694
|
+
return path.join(root, parentName, "docs/business-flow", parts[0], "story", `${parts[1]}.md`);
|
|
695
|
+
}
|
|
696
|
+
if (MODULE_DIR_MAP[type]) return path.join(root, parentName, MODULE_DIR_MAP[type], `${name}.md`);
|
|
697
|
+
if (APP_DIR_MAP[type]) return path.join(root, parentName, APP_DIR_MAP[type], `${name}.md`);
|
|
698
|
+
throw new Error(`Unknown scaffold type: ${type}`);
|
|
699
|
+
}
|
|
700
|
+
async function runScaffold(type, parentName, name, root, cwd) {
|
|
701
|
+
const outputPath = resolveScaffoldPath(type, parentName, name, root);
|
|
702
|
+
const absoluteOutput = path.resolve(cwd, outputPath);
|
|
703
|
+
if (fs.existsSync(absoluteOutput)) {
|
|
704
|
+
console.error(`File already exists: ${outputPath}`);
|
|
705
|
+
return 1;
|
|
706
|
+
}
|
|
707
|
+
const schemaPath = ALL_SCHEMAS[type];
|
|
708
|
+
if (!schemaPath) {
|
|
709
|
+
console.error(`No schema found for type: ${type}`);
|
|
710
|
+
return 2;
|
|
711
|
+
}
|
|
712
|
+
try {
|
|
713
|
+
fs.mkdirSync(path.dirname(absoluteOutput), { recursive: true });
|
|
714
|
+
} catch (err) {
|
|
715
|
+
console.error(`Failed to create directory: ${err instanceof Error ? err.message : String(err)}`);
|
|
716
|
+
return 1;
|
|
717
|
+
}
|
|
718
|
+
const { exitCode, stdout, stderr } = await runMdschema([
|
|
719
|
+
"generate",
|
|
720
|
+
"--schema",
|
|
721
|
+
schemaPath,
|
|
722
|
+
"--output",
|
|
723
|
+
absoluteOutput
|
|
724
|
+
], cwd);
|
|
725
|
+
if (stdout.trim()) console.log(stdout);
|
|
726
|
+
if (stderr.trim()) console.error(stderr);
|
|
727
|
+
if (exitCode !== 0) return exitCode;
|
|
728
|
+
if (type === "module") scaffoldModuleSrc(path.dirname(absoluteOutput), parentName);
|
|
729
|
+
if (type === "app") scaffoldAppSrc(path.dirname(absoluteOutput), parentName);
|
|
730
|
+
return exitCode;
|
|
731
|
+
}
|
|
732
|
+
function copyTemplateDir(srcDir, destDir, replacements, placeholderFiles) {
|
|
733
|
+
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
734
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
735
|
+
const destName = entry.name === "__dot__gitignore" ? ".gitignore" : entry.name;
|
|
736
|
+
const destPath = path.join(destDir, destName);
|
|
737
|
+
if (entry.isDirectory()) {
|
|
738
|
+
fs.mkdirSync(destPath, { recursive: true });
|
|
739
|
+
copyTemplateDir(srcPath, destPath, replacements, placeholderFiles);
|
|
740
|
+
} else {
|
|
741
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
742
|
+
if (placeholderFiles.has(entry.name)) {
|
|
743
|
+
let content = fs.readFileSync(srcPath, "utf-8");
|
|
744
|
+
for (const [from, to] of Object.entries(replacements)) content = content.replaceAll(from, to);
|
|
745
|
+
fs.writeFileSync(destPath, content);
|
|
746
|
+
} else fs.copyFileSync(srcPath, destPath);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
function scaffoldModuleSrc(moduleDir, moduleName) {
|
|
751
|
+
copyTemplateDir(path.join(PACKAGE_ROOT, "templates", "scaffold", "module"), moduleDir, { "template-module": moduleName }, new Set(["permissions.ts", "tailor.config.ts"]));
|
|
752
|
+
}
|
|
753
|
+
function scaffoldAppSrc(appDir, appName) {
|
|
754
|
+
const templateDir = path.join(PACKAGE_ROOT, "templates", "scaffold", "app");
|
|
755
|
+
const erpKitVersion = readErpKitVersion();
|
|
756
|
+
copyTemplateDir(templateDir, appDir, {
|
|
757
|
+
"template-app-frontend": `${appName}-frontend`,
|
|
758
|
+
"template-app-backend": appName,
|
|
759
|
+
"template-app": appName,
|
|
760
|
+
"\"workspace:*\"": `"${erpKitVersion}"`
|
|
761
|
+
}, new Set([
|
|
762
|
+
"package.json",
|
|
763
|
+
"tailor.config.ts",
|
|
764
|
+
"index.html"
|
|
765
|
+
]));
|
|
766
|
+
}
|
|
767
|
+
//#endregion
|
|
768
|
+
//#region src/commands/module/list.ts
|
|
769
|
+
const MODULES_DIR = join(PACKAGE_ROOT, "src", "modules");
|
|
770
|
+
const EXCLUDED_DIRS = new Set(["shared", "testing"]);
|
|
771
|
+
function countFiles(dir, pattern, exclusions) {
|
|
772
|
+
if (!existsSync(dir)) return 0;
|
|
773
|
+
return readdirSync(dir).filter((f) => pattern.test(f) && !exclusions.some((p) => p.test(f))).length;
|
|
774
|
+
}
|
|
775
|
+
function listModules() {
|
|
776
|
+
if (!existsSync(MODULES_DIR)) return [];
|
|
777
|
+
return readdirSync(MODULES_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && !EXCLUDED_DIRS.has(d.name)).map((d) => {
|
|
778
|
+
const modDir = join(MODULES_DIR, d.name);
|
|
779
|
+
return {
|
|
780
|
+
name: d.name,
|
|
781
|
+
commands: countFiles(join(modDir, "command"), /\.ts$/, [/\.test\.ts$/]),
|
|
782
|
+
models: countFiles(join(modDir, "db"), /\.ts$/, [/\.test\.ts$/, /^index\.ts$/]),
|
|
783
|
+
features: countFiles(join(modDir, "docs", "features"), /\.md$/, [])
|
|
784
|
+
};
|
|
785
|
+
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
786
|
+
}
|
|
787
|
+
function formatModuleList(modules) {
|
|
788
|
+
if (modules.length === 0) return "No modules found.";
|
|
789
|
+
const lines = [];
|
|
790
|
+
lines.push(chalk.bold("Modules:\n"));
|
|
791
|
+
const nameWidth = Math.max(...modules.map((m) => m.name.length), 4);
|
|
792
|
+
for (const mod of modules) {
|
|
793
|
+
const counts = [
|
|
794
|
+
`${mod.commands} commands`,
|
|
795
|
+
`${mod.models} models`,
|
|
796
|
+
`${mod.features} features`
|
|
797
|
+
].join(", ");
|
|
798
|
+
lines.push(` ${mod.name.padEnd(nameWidth)} ${counts}`);
|
|
799
|
+
}
|
|
800
|
+
lines.push("");
|
|
801
|
+
lines.push(`${modules.length} modules`);
|
|
802
|
+
return lines.join("\n");
|
|
803
|
+
}
|
|
804
|
+
function runModuleList() {
|
|
805
|
+
const modules = listModules();
|
|
806
|
+
console.log(formatModuleList(modules));
|
|
807
|
+
return 0;
|
|
808
|
+
}
|
|
809
|
+
//#endregion
|
|
810
|
+
//#region src/generator/parse-command-doc.ts
|
|
811
|
+
function parseCommandDoc(fileName, markdown) {
|
|
812
|
+
const commandName = fileName.charAt(0).toLowerCase() + fileName.slice(1);
|
|
813
|
+
const tree = fromMarkdown(markdown);
|
|
814
|
+
return {
|
|
815
|
+
commandName,
|
|
816
|
+
errors: parseErrorScenarios(tree),
|
|
817
|
+
externalDependencies: parseExternalDependencies(tree)
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
function errorCodeToClassName(code) {
|
|
821
|
+
return code.toLowerCase().split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("") + "Error";
|
|
822
|
+
}
|
|
823
|
+
function isHeading(node) {
|
|
824
|
+
return node.type === "heading";
|
|
825
|
+
}
|
|
826
|
+
function isList(node) {
|
|
827
|
+
return node.type === "list";
|
|
828
|
+
}
|
|
829
|
+
function getNodesUnderHeading(tree, headingText) {
|
|
830
|
+
const nodes = [];
|
|
831
|
+
let collecting = false;
|
|
832
|
+
for (const node of tree.children) {
|
|
833
|
+
if (isHeading(node)) {
|
|
834
|
+
if (collecting) break;
|
|
835
|
+
if (node.depth === 2 && toString(node) === headingText) {
|
|
836
|
+
collecting = true;
|
|
837
|
+
continue;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
if (collecting) nodes.push(node);
|
|
841
|
+
}
|
|
842
|
+
return nodes;
|
|
843
|
+
}
|
|
844
|
+
const ERROR_PATTERN = /^([A-Z_]+):\s*(.+)$/;
|
|
845
|
+
function parseErrorScenarios(tree) {
|
|
846
|
+
const nodes = getNodesUnderHeading(tree, "Error Scenarios");
|
|
847
|
+
const errors = [];
|
|
848
|
+
for (const node of nodes) {
|
|
849
|
+
if (!isList(node)) continue;
|
|
850
|
+
for (const item of node.children) {
|
|
851
|
+
const text = toString(item);
|
|
852
|
+
const match = ERROR_PATTERN.exec(text);
|
|
853
|
+
if (match) errors.push({
|
|
854
|
+
code: match[1],
|
|
855
|
+
description: match[2].trim()
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
return errors;
|
|
860
|
+
}
|
|
861
|
+
const DEPENDENCY_PATTERN = /^([^:]+)::(.+)$/;
|
|
862
|
+
function parseExternalDependencies(tree) {
|
|
863
|
+
const nodes = getNodesUnderHeading(tree, "External Dependencies");
|
|
864
|
+
const deps = [];
|
|
865
|
+
for (const node of nodes) {
|
|
866
|
+
if (!isList(node)) continue;
|
|
867
|
+
for (const item of node.children) {
|
|
868
|
+
const firstChild = item.children[0];
|
|
869
|
+
if (firstChild?.type !== "paragraph") continue;
|
|
870
|
+
for (const inline of firstChild.children) if (inline.type === "link" || inline.type === "linkReference") {
|
|
871
|
+
const linkText = toString(inline);
|
|
872
|
+
const match = DEPENDENCY_PATTERN.exec(linkText);
|
|
873
|
+
if (match) deps.push({
|
|
874
|
+
module: match[1],
|
|
875
|
+
entity: match[2]
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
return deps;
|
|
881
|
+
}
|
|
882
|
+
//#endregion
|
|
883
|
+
//#region src/generator/generate-code.ts
|
|
884
|
+
function moduleNameToPrefix(moduleName) {
|
|
885
|
+
return moduleName.toUpperCase().replace(/-/g, "_");
|
|
886
|
+
}
|
|
887
|
+
function generateErrors(moduleName, docs) {
|
|
888
|
+
const seen = /* @__PURE__ */ new Set();
|
|
889
|
+
const lines = [];
|
|
890
|
+
const prefix = moduleNameToPrefix(moduleName);
|
|
891
|
+
for (const doc of docs) for (const error of doc.errors) {
|
|
892
|
+
if (seen.has(error.code)) continue;
|
|
893
|
+
seen.add(error.code);
|
|
894
|
+
const className = errorCodeToClassName(error.code);
|
|
895
|
+
const prefixedCode = `${prefix}_${error.code}`;
|
|
896
|
+
lines.push(`export const ${className} = createDomainError(`);
|
|
897
|
+
lines.push(` "${className}", "${prefixedCode}",`);
|
|
898
|
+
const escapedDesc = error.description.replace(/`/g, "'");
|
|
899
|
+
lines.push(` (identifier: string) => \`${escapedDesc}: \${identifier}\`,`);
|
|
900
|
+
lines.push(`);`);
|
|
901
|
+
lines.push(``);
|
|
902
|
+
}
|
|
903
|
+
if (lines.length === 0) return "";
|
|
904
|
+
return `// @generated — do not edit
|
|
905
|
+
import { createDomainError } from "../../../shared";
|
|
906
|
+
|
|
907
|
+
${lines.join("\n")}`;
|
|
908
|
+
}
|
|
909
|
+
function generateCommandStub(doc) {
|
|
910
|
+
const inputType = `${doc.commandName.charAt(0).toUpperCase() + doc.commandName.slice(1)}Input`;
|
|
911
|
+
return `import { ok, type CommandContext } from "../../../shared";
|
|
912
|
+
import type { Transaction } from "../generated/kysely-tailordb";
|
|
913
|
+
|
|
914
|
+
export interface ${inputType} {
|
|
915
|
+
// TODO: define input fields
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
export async function run(db: Transaction, input: ${inputType}, ctx: CommandContext) {
|
|
919
|
+
// TODO: implement
|
|
920
|
+
return ok({});
|
|
921
|
+
}
|
|
922
|
+
`;
|
|
923
|
+
}
|
|
924
|
+
function generateTestStub(doc) {
|
|
925
|
+
const pascal = doc.commandName.charAt(0).toUpperCase() + doc.commandName.slice(1);
|
|
926
|
+
return `import { describe, expect, it } from "vitest";
|
|
927
|
+
import { createMockDb } from "../../../testing/index";
|
|
928
|
+
import type { CommandContext } from "../../../shared";
|
|
929
|
+
import type { Transaction } from "../generated/kysely-tailordb";
|
|
930
|
+
import { run, ${pascal}Input } from "./${doc.commandName}";
|
|
931
|
+
|
|
932
|
+
describe("${doc.commandName} - test scenario", () => {
|
|
933
|
+
const ctx: CommandContext = {
|
|
934
|
+
actorId: "test-actor",
|
|
935
|
+
permissions: ["TODO:${doc.commandName}"],
|
|
936
|
+
};
|
|
937
|
+
|
|
938
|
+
it("should be implemented", async () => {
|
|
939
|
+
const { db } = createMockDb<Transaction>();
|
|
940
|
+
const result = await run(db, {} as ${pascal}Input, ctx);
|
|
941
|
+
expect(result.ok).toBe(true);
|
|
942
|
+
});
|
|
943
|
+
});
|
|
944
|
+
`;
|
|
945
|
+
}
|
|
946
|
+
function generateCommandShell(doc) {
|
|
947
|
+
return [
|
|
948
|
+
`// @generated — do not edit`,
|
|
949
|
+
`import { defineCommand } from "../../../shared";`,
|
|
950
|
+
`import { permissions } from "../lib/permissions.generated";`,
|
|
951
|
+
`import { run } from "./${doc.commandName}";`,
|
|
952
|
+
``,
|
|
953
|
+
`export const ${doc.commandName} = defineCommand(permissions.${doc.commandName}, run);`,
|
|
954
|
+
``
|
|
955
|
+
].join("\n");
|
|
956
|
+
}
|
|
957
|
+
function generateQueryShell(doc) {
|
|
958
|
+
return [
|
|
959
|
+
`// @generated — do not edit`,
|
|
960
|
+
`import { defineQuery } from "../../../shared";`,
|
|
961
|
+
`import { run } from "./${doc.commandName}";`,
|
|
962
|
+
``,
|
|
963
|
+
`export const ${doc.commandName} = defineQuery(run);`,
|
|
964
|
+
``
|
|
965
|
+
].join("\n");
|
|
966
|
+
}
|
|
967
|
+
function generateQueryStub(doc) {
|
|
968
|
+
const inputType = `${doc.commandName.charAt(0).toUpperCase() + doc.commandName.slice(1)}Input`;
|
|
969
|
+
return `import type { ReadonlyDB } from "../../../shared";
|
|
970
|
+
import type { DB } from "../generated/kysely-tailordb";
|
|
971
|
+
|
|
972
|
+
export interface ${inputType} {
|
|
973
|
+
// TODO: define input fields
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
export async function run(db: ReadonlyDB<DB>, input: ${inputType}) {
|
|
977
|
+
// TODO: implement
|
|
978
|
+
return {};
|
|
979
|
+
}
|
|
980
|
+
`;
|
|
981
|
+
}
|
|
982
|
+
function generateQueryTestStub(doc) {
|
|
983
|
+
const inputType = `${doc.commandName.charAt(0).toUpperCase() + doc.commandName.slice(1)}Input`;
|
|
984
|
+
return `import { describe, expect, it } from "vitest";
|
|
985
|
+
import { createMockDb } from "../../../testing/index";
|
|
986
|
+
import type { DB } from "../generated/kysely-tailordb";
|
|
987
|
+
import { run, type ${inputType} } from "./${doc.commandName}";
|
|
988
|
+
|
|
989
|
+
describe("${doc.commandName}", () => {
|
|
990
|
+
it("should be implemented", async () => {
|
|
991
|
+
const { db } = createMockDb<DB>();
|
|
992
|
+
const result = await run(db, {} as ${inputType});
|
|
993
|
+
expect(result).toBeDefined();
|
|
994
|
+
});
|
|
995
|
+
});
|
|
996
|
+
`;
|
|
997
|
+
}
|
|
998
|
+
function generatePermissions(moduleName, commandNames) {
|
|
999
|
+
return `// @generated — do not edit
|
|
1000
|
+
import { definePermissions } from "../../../shared";
|
|
1001
|
+
|
|
1002
|
+
export const { permissions, own, all } = definePermissions("${moduleName}", [
|
|
1003
|
+
${[...commandNames].sort().map((name) => ` "${name}",`).join("\n")}
|
|
1004
|
+
] as const);
|
|
1005
|
+
`;
|
|
1006
|
+
}
|
|
1007
|
+
function runGenerateCode(modulePath, moduleName) {
|
|
1008
|
+
const docsDir = path.join(modulePath, "docs", "commands");
|
|
1009
|
+
const libDir = path.join(modulePath, "lib");
|
|
1010
|
+
const commandDir = path.join(modulePath, "command");
|
|
1011
|
+
if (!fs.existsSync(docsDir)) {
|
|
1012
|
+
console.error(`No docs/commands/ directory found at ${docsDir}`);
|
|
1013
|
+
return 1;
|
|
1014
|
+
}
|
|
1015
|
+
const mdFiles = fs.readdirSync(docsDir).filter((f) => f.endsWith(".md"));
|
|
1016
|
+
if (mdFiles.length === 0) {
|
|
1017
|
+
console.error(`No command docs found in ${docsDir}`);
|
|
1018
|
+
return 1;
|
|
1019
|
+
}
|
|
1020
|
+
const parsedDocs = [];
|
|
1021
|
+
for (const file of mdFiles) {
|
|
1022
|
+
const content = fs.readFileSync(path.join(docsDir, file), "utf-8");
|
|
1023
|
+
const name = path.basename(file, ".md");
|
|
1024
|
+
parsedDocs.push(parseCommandDoc(name, content));
|
|
1025
|
+
}
|
|
1026
|
+
const queryDocsDir = path.join(modulePath, "docs", "queries");
|
|
1027
|
+
const allDocsForErrors = [...parsedDocs];
|
|
1028
|
+
if (fs.existsSync(queryDocsDir)) {
|
|
1029
|
+
const queryFiles = fs.readdirSync(queryDocsDir).filter((f) => f.endsWith(".md"));
|
|
1030
|
+
for (const file of queryFiles) {
|
|
1031
|
+
const content = fs.readFileSync(path.join(queryDocsDir, file), "utf-8");
|
|
1032
|
+
const name = path.basename(file, ".md");
|
|
1033
|
+
allDocsForErrors.push(parseCommandDoc(name, content));
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
let generated = 0;
|
|
1037
|
+
const errorsContent = generateErrors(moduleName, allDocsForErrors);
|
|
1038
|
+
if (errorsContent) {
|
|
1039
|
+
fs.mkdirSync(libDir, { recursive: true });
|
|
1040
|
+
const errorsFile = path.join(libDir, "errors.generated.ts");
|
|
1041
|
+
fs.writeFileSync(errorsFile, errorsContent);
|
|
1042
|
+
console.log(` wrote ${path.relative(modulePath, errorsFile)}`);
|
|
1043
|
+
generated++;
|
|
1044
|
+
}
|
|
1045
|
+
const permissionsContent = generatePermissions(moduleName, parsedDocs.map((d) => d.commandName));
|
|
1046
|
+
const permissionsFile = path.join(libDir, "permissions.generated.ts");
|
|
1047
|
+
fs.writeFileSync(permissionsFile, permissionsContent);
|
|
1048
|
+
console.log(` wrote ${path.relative(modulePath, permissionsFile)}`);
|
|
1049
|
+
generated++;
|
|
1050
|
+
fs.mkdirSync(commandDir, { recursive: true });
|
|
1051
|
+
for (const doc of parsedDocs) {
|
|
1052
|
+
const implFile = path.join(commandDir, `${doc.commandName}.ts`);
|
|
1053
|
+
const shellContent = generateCommandShell(doc);
|
|
1054
|
+
const shellFile = path.join(commandDir, `${doc.commandName}.generated.ts`);
|
|
1055
|
+
fs.writeFileSync(shellFile, shellContent);
|
|
1056
|
+
console.log(` wrote ${path.relative(modulePath, shellFile)}`);
|
|
1057
|
+
generated++;
|
|
1058
|
+
if (!fs.existsSync(implFile)) {
|
|
1059
|
+
fs.writeFileSync(implFile, generateCommandStub(doc));
|
|
1060
|
+
console.log(` scaffolded ${path.relative(modulePath, implFile)}`);
|
|
1061
|
+
}
|
|
1062
|
+
const testFile = path.join(commandDir, `${doc.commandName}.test.ts`);
|
|
1063
|
+
if (!fs.existsSync(testFile)) {
|
|
1064
|
+
fs.writeFileSync(testFile, generateTestStub(doc));
|
|
1065
|
+
console.log(` scaffolded ${path.relative(modulePath, testFile)}`);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
if (fs.existsSync(queryDocsDir)) {
|
|
1069
|
+
const queryFiles = fs.readdirSync(queryDocsDir).filter((f) => f.endsWith(".md"));
|
|
1070
|
+
if (queryFiles.length > 0) {
|
|
1071
|
+
const queryDir = path.join(modulePath, "query");
|
|
1072
|
+
fs.mkdirSync(queryDir, { recursive: true });
|
|
1073
|
+
for (const file of queryFiles) {
|
|
1074
|
+
const content = fs.readFileSync(path.join(queryDocsDir, file), "utf-8");
|
|
1075
|
+
const doc = parseCommandDoc(path.basename(file, ".md"), content);
|
|
1076
|
+
const shellFile = path.join(queryDir, `${doc.commandName}.generated.ts`);
|
|
1077
|
+
fs.writeFileSync(shellFile, generateQueryShell(doc));
|
|
1078
|
+
console.log(` wrote ${path.relative(modulePath, shellFile)}`);
|
|
1079
|
+
generated++;
|
|
1080
|
+
const implFile = path.join(queryDir, `${doc.commandName}.ts`);
|
|
1081
|
+
if (!fs.existsSync(implFile)) {
|
|
1082
|
+
fs.writeFileSync(implFile, generateQueryStub(doc));
|
|
1083
|
+
console.log(` scaffolded ${path.relative(modulePath, implFile)}`);
|
|
1084
|
+
}
|
|
1085
|
+
const testFile = path.join(queryDir, `${doc.commandName}.test.ts`);
|
|
1086
|
+
if (!fs.existsSync(testFile)) {
|
|
1087
|
+
fs.writeFileSync(testFile, generateQueryTestStub(doc));
|
|
1088
|
+
console.log(` scaffolded ${path.relative(modulePath, testFile)}`);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
console.log(`Generated ${generated} file(s) for ${moduleName}`);
|
|
1094
|
+
return 0;
|
|
1095
|
+
}
|
|
1096
|
+
//#endregion
|
|
1097
|
+
//#region src/commands/module/generate.ts
|
|
1098
|
+
const cwd$3 = process.cwd();
|
|
1099
|
+
const generateCommand = defineCommand({
|
|
1100
|
+
name: "generate",
|
|
1101
|
+
description: "Generate code from model definitions and docs",
|
|
1102
|
+
subCommands: { code: defineCommand({
|
|
1103
|
+
name: "code",
|
|
1104
|
+
description: "Generate errors, permissions, command shells, and query shells from docs",
|
|
1105
|
+
args: z.object({
|
|
1106
|
+
root: arg(z.string(), {
|
|
1107
|
+
alias: "r",
|
|
1108
|
+
description: "Path to modules directory"
|
|
1109
|
+
}),
|
|
1110
|
+
module: arg(z.string(), {
|
|
1111
|
+
positional: true,
|
|
1112
|
+
description: "Module name (e.g., primitives, item-management)"
|
|
1113
|
+
})
|
|
1114
|
+
}),
|
|
1115
|
+
run: (args) => {
|
|
1116
|
+
const modulePath = path.resolve(cwd$3, args.root, args.module);
|
|
1117
|
+
console.log(`Generating code for ${args.module}...`);
|
|
1118
|
+
const exitCode = runGenerateCode(modulePath, args.module);
|
|
1119
|
+
process.exit(exitCode);
|
|
1120
|
+
}
|
|
1121
|
+
}) }
|
|
1122
|
+
});
|
|
1123
|
+
//#endregion
|
|
1124
|
+
//#region src/commands/module/index.ts
|
|
1125
|
+
const cwd$2 = process.cwd();
|
|
1126
|
+
const rootArgs$1 = z.object({ root: arg(z.string(), {
|
|
1127
|
+
alias: "r",
|
|
1128
|
+
description: "Path to modules directory"
|
|
1129
|
+
}) });
|
|
1130
|
+
const listCommand = defineCommand({
|
|
1131
|
+
name: "list",
|
|
1132
|
+
description: "List available modules",
|
|
1133
|
+
run: () => {
|
|
1134
|
+
const exitCode = runModuleList();
|
|
1135
|
+
process.exit(exitCode);
|
|
1136
|
+
}
|
|
1137
|
+
});
|
|
1138
|
+
const checkCommand$1 = defineCommand({
|
|
1139
|
+
name: "check",
|
|
1140
|
+
description: "Validate module docs against schemas",
|
|
1141
|
+
args: rootArgs$1,
|
|
1142
|
+
run: async (args) => {
|
|
1143
|
+
const exitCode = await runCheck({ modulesRoot: args.root }, cwd$2);
|
|
1144
|
+
process.exit(exitCode);
|
|
1145
|
+
}
|
|
1146
|
+
});
|
|
1147
|
+
const syncCheckCommand$1 = defineCommand({
|
|
1148
|
+
name: "sync-check",
|
|
1149
|
+
description: "Validate source <-> doc correspondence",
|
|
1150
|
+
args: rootArgs$1,
|
|
1151
|
+
run: async (args) => {
|
|
1152
|
+
const result = await runSyncCheck({ modulesRoot: args.root }, cwd$2);
|
|
1153
|
+
console.log(formatSyncCheckReport(result));
|
|
1154
|
+
process.exit(result.exitCode);
|
|
1155
|
+
}
|
|
1156
|
+
});
|
|
1157
|
+
const scaffoldCommand$1 = defineCommand({
|
|
1158
|
+
name: "scaffold",
|
|
1159
|
+
description: "Generate module doc from schema template",
|
|
1160
|
+
args: rootArgs$1.extend({
|
|
1161
|
+
type: arg(z.enum(MODULE_TYPES), {
|
|
1162
|
+
positional: true,
|
|
1163
|
+
description: `Scaffold type (${MODULE_TYPES.join(", ")})`
|
|
1164
|
+
}),
|
|
1165
|
+
parent: arg(z.string(), {
|
|
1166
|
+
positional: true,
|
|
1167
|
+
description: "Module name"
|
|
1168
|
+
}),
|
|
1169
|
+
name: arg(z.string().optional(), {
|
|
1170
|
+
positional: true,
|
|
1171
|
+
description: "Item name (required for feature, command, model)"
|
|
1172
|
+
})
|
|
1173
|
+
}),
|
|
1174
|
+
run: async (args) => {
|
|
1175
|
+
const exitCode = await runScaffold(args.type, args.parent, args.name, args.root, cwd$2);
|
|
1176
|
+
process.exit(exitCode);
|
|
1177
|
+
}
|
|
1178
|
+
});
|
|
1179
|
+
const moduleCommand = defineCommand({
|
|
1180
|
+
name: "module",
|
|
1181
|
+
description: "Module management",
|
|
1182
|
+
subCommands: {
|
|
1183
|
+
list: listCommand,
|
|
1184
|
+
check: checkCommand$1,
|
|
1185
|
+
"sync-check": syncCheckCommand$1,
|
|
1186
|
+
scaffold: scaffoldCommand$1,
|
|
1187
|
+
generate: generateCommand
|
|
1188
|
+
}
|
|
1189
|
+
});
|
|
1190
|
+
//#endregion
|
|
1191
|
+
//#region src/commands/app/index.ts
|
|
1192
|
+
const cwd$1 = process.cwd();
|
|
1193
|
+
const rootArgs = z.object({ root: arg(z.string(), {
|
|
1194
|
+
alias: "r",
|
|
1195
|
+
description: "Path to app-compose directory"
|
|
1196
|
+
}) });
|
|
1197
|
+
const checkCommand = defineCommand({
|
|
1198
|
+
name: "check",
|
|
1199
|
+
description: "Validate app docs against schemas",
|
|
1200
|
+
args: rootArgs,
|
|
1201
|
+
run: async (args) => {
|
|
1202
|
+
const exitCode = await runCheck({ appRoot: args.root }, cwd$1);
|
|
1203
|
+
process.exit(exitCode);
|
|
1204
|
+
}
|
|
1205
|
+
});
|
|
1206
|
+
const syncCheckCommand = defineCommand({
|
|
1207
|
+
name: "sync-check",
|
|
1208
|
+
description: "Validate source <-> doc correspondence",
|
|
1209
|
+
args: rootArgs,
|
|
1210
|
+
run: async (args) => {
|
|
1211
|
+
const result = await runSyncCheck({ appRoot: args.root }, cwd$1);
|
|
1212
|
+
console.log(formatSyncCheckReport(result));
|
|
1213
|
+
process.exit(result.exitCode);
|
|
1214
|
+
}
|
|
1215
|
+
});
|
|
1216
|
+
const scaffoldCommand = defineCommand({
|
|
1217
|
+
name: "scaffold",
|
|
1218
|
+
description: "Generate app doc from schema template",
|
|
1219
|
+
args: rootArgs.extend({
|
|
1220
|
+
type: arg(z.enum(APP_TYPES), {
|
|
1221
|
+
positional: true,
|
|
1222
|
+
description: `Scaffold type (${APP_TYPES.join(", ")})`
|
|
1223
|
+
}),
|
|
1224
|
+
parent: arg(z.string(), {
|
|
1225
|
+
positional: true,
|
|
1226
|
+
description: "App name"
|
|
1227
|
+
}),
|
|
1228
|
+
name: arg(z.string().optional(), {
|
|
1229
|
+
positional: true,
|
|
1230
|
+
description: "Item name (required for most types)"
|
|
1231
|
+
})
|
|
1232
|
+
}),
|
|
1233
|
+
run: async (args) => {
|
|
1234
|
+
const exitCode = await runScaffold(args.type, args.parent, args.name, args.root, cwd$1);
|
|
1235
|
+
process.exit(exitCode);
|
|
1236
|
+
}
|
|
1237
|
+
});
|
|
1238
|
+
const appCommand = defineCommand({
|
|
1239
|
+
name: "app",
|
|
1240
|
+
description: "App-compose management",
|
|
1241
|
+
subCommands: {
|
|
1242
|
+
check: checkCommand,
|
|
1243
|
+
"sync-check": syncCheckCommand,
|
|
1244
|
+
scaffold: scaffoldCommand
|
|
1245
|
+
}
|
|
1246
|
+
});
|
|
1247
|
+
//#endregion
|
|
1248
|
+
//#region src/mockServer.ts
|
|
1249
|
+
/**
|
|
1250
|
+
* Start a Mockoon mock server from a mock.json file.
|
|
1251
|
+
* Returns a handle with the server URL and a stop function.
|
|
1252
|
+
*/
|
|
1253
|
+
async function createMockServer(mockJsonPath, port) {
|
|
1254
|
+
const { MockoonServer, createLoggerInstance, listenServerEvents } = await import("@mockoon/commons-server");
|
|
1255
|
+
const resolvedPath = resolve(mockJsonPath);
|
|
1256
|
+
const environment = JSON.parse(readFileSync(resolvedPath, "utf-8"));
|
|
1257
|
+
if (port !== void 0) environment.port = port;
|
|
1258
|
+
const actualPort = environment.port;
|
|
1259
|
+
const logger = createLoggerInstance(null);
|
|
1260
|
+
const server = new MockoonServer(environment, {
|
|
1261
|
+
environmentDirectory: dirname(resolvedPath),
|
|
1262
|
+
enableAdminApi: false,
|
|
1263
|
+
disableTls: true,
|
|
1264
|
+
enableRandomLatency: false,
|
|
1265
|
+
maxTransactionLogs: 100,
|
|
1266
|
+
envVarsPrefix: "MOCKOON_"
|
|
1267
|
+
});
|
|
1268
|
+
listenServerEvents(server, environment, logger, false);
|
|
1269
|
+
await new Promise((resolve, reject) => {
|
|
1270
|
+
server.on("started", resolve);
|
|
1271
|
+
server.on("error", (errorCode, originalError) => {
|
|
1272
|
+
reject(originalError ?? /* @__PURE__ */ new Error(`Mockoon error: ${errorCode}`));
|
|
1273
|
+
});
|
|
1274
|
+
server.start();
|
|
1275
|
+
});
|
|
1276
|
+
return {
|
|
1277
|
+
url: `http://127.0.0.1:${actualPort}`,
|
|
1278
|
+
port: actualPort,
|
|
1279
|
+
stop: () => new Promise((resolve) => {
|
|
1280
|
+
server.on("stopped", resolve);
|
|
1281
|
+
server.stop();
|
|
1282
|
+
})
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
//#endregion
|
|
1286
|
+
//#region src/commands/mock/start.ts
|
|
1287
|
+
function readdirSafe(dir) {
|
|
1288
|
+
try {
|
|
1289
|
+
return readdirSync(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
1290
|
+
} catch {
|
|
1291
|
+
return [];
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
function discoverMocks(mocksDir, filters) {
|
|
1295
|
+
const mocks = [];
|
|
1296
|
+
for (const provider of readdirSafe(mocksDir)) {
|
|
1297
|
+
const providerDir = join(mocksDir, provider);
|
|
1298
|
+
for (const scenario of readdirSafe(providerDir)) {
|
|
1299
|
+
const mockPath = join(providerDir, scenario, "mock.json");
|
|
1300
|
+
if (!existsSync(mockPath)) continue;
|
|
1301
|
+
mocks.push({
|
|
1302
|
+
provider,
|
|
1303
|
+
scenario,
|
|
1304
|
+
mockPath
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
if (filters.length === 0) return mocks;
|
|
1309
|
+
return mocks.filter(({ provider, scenario }) => filters.some((f) => f === provider || f === `${provider}/${scenario}`));
|
|
1310
|
+
}
|
|
1311
|
+
function findFreePort() {
|
|
1312
|
+
return new Promise((resolve, reject) => {
|
|
1313
|
+
const srv = createServer$1();
|
|
1314
|
+
srv.listen(0, "127.0.0.1", () => {
|
|
1315
|
+
const addr = srv.address();
|
|
1316
|
+
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
1317
|
+
srv.close(() => resolve(port));
|
|
1318
|
+
});
|
|
1319
|
+
srv.on("error", reject);
|
|
1320
|
+
});
|
|
1321
|
+
}
|
|
1322
|
+
async function startMock(mock) {
|
|
1323
|
+
const port = await findFreePort();
|
|
1324
|
+
return {
|
|
1325
|
+
server: await createMockServer(mock.mockPath, port),
|
|
1326
|
+
route: `/${mock.provider}/${mock.scenario}`,
|
|
1327
|
+
provider: mock.provider,
|
|
1328
|
+
scenario: mock.scenario
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
function createProxy(routeTable) {
|
|
1332
|
+
return createServer((req, res) => {
|
|
1333
|
+
const match = req.url?.match(/^\/([^/?]+)\/([^/?]+)(\/[^?]*)?(\?.*)?$/);
|
|
1334
|
+
if (!match) {
|
|
1335
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1336
|
+
res.end(JSON.stringify({ error: "Not found. Use /{provider}/{scenario}/..." }));
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
const [, provider, scenario] = match;
|
|
1340
|
+
const key = `/${provider}/${scenario}`;
|
|
1341
|
+
const target = routeTable.get(key);
|
|
1342
|
+
if (!target) {
|
|
1343
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1344
|
+
res.end(JSON.stringify({
|
|
1345
|
+
error: `Unknown route: ${key}`,
|
|
1346
|
+
available: [...routeTable.keys()]
|
|
1347
|
+
}));
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
const downstream = (req.url ?? "/").slice(key.length) || "/";
|
|
1351
|
+
const proxyReq = request({
|
|
1352
|
+
hostname: "127.0.0.1",
|
|
1353
|
+
port: target.server.port,
|
|
1354
|
+
path: downstream,
|
|
1355
|
+
method: req.method,
|
|
1356
|
+
headers: {
|
|
1357
|
+
...req.headers,
|
|
1358
|
+
host: `127.0.0.1:${target.server.port}`
|
|
1359
|
+
}
|
|
1360
|
+
}, (proxyRes) => {
|
|
1361
|
+
res.writeHead(proxyRes.statusCode ?? 502, proxyRes.headers);
|
|
1362
|
+
proxyRes.pipe(res);
|
|
1363
|
+
});
|
|
1364
|
+
proxyReq.on("error", (err) => {
|
|
1365
|
+
if (!res.headersSent) res.writeHead(502, { "Content-Type": "application/json" });
|
|
1366
|
+
res.end(JSON.stringify({
|
|
1367
|
+
error: "Bad gateway",
|
|
1368
|
+
detail: err.message
|
|
1369
|
+
}));
|
|
1370
|
+
});
|
|
1371
|
+
req.pipe(proxyReq);
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
async function runMockStart(mocksRoot, filters, port) {
|
|
1375
|
+
const mocksDir = resolve(mocksRoot);
|
|
1376
|
+
const mocks = discoverMocks(mocksDir, filters);
|
|
1377
|
+
if (mocks.length === 0) {
|
|
1378
|
+
console.error("No matching mocks found.");
|
|
1379
|
+
if (filters.length > 0) {
|
|
1380
|
+
console.error(`Filters: ${filters.join(", ")}`);
|
|
1381
|
+
console.error(`Available mocks are under: ${relative(process.cwd(), mocksDir)}/`);
|
|
1382
|
+
}
|
|
1383
|
+
return 1;
|
|
1384
|
+
}
|
|
1385
|
+
console.log(`Starting ${mocks.length} mock(s)...\n`);
|
|
1386
|
+
const running = [];
|
|
1387
|
+
for (const mock of mocks) {
|
|
1388
|
+
const info = await startMock(mock);
|
|
1389
|
+
running.push(info);
|
|
1390
|
+
}
|
|
1391
|
+
const routeTable = /* @__PURE__ */ new Map();
|
|
1392
|
+
for (const r of running) routeTable.set(r.route, r);
|
|
1393
|
+
console.log("Route table:");
|
|
1394
|
+
console.log("─".repeat(50));
|
|
1395
|
+
for (const [route, { server }] of routeTable) console.log(` ${route} \u2192 127.0.0.1:${server.port}`);
|
|
1396
|
+
console.log("─".repeat(50));
|
|
1397
|
+
const proxy = createProxy(routeTable);
|
|
1398
|
+
proxy.listen(port, () => {
|
|
1399
|
+
console.log(`\nReverse proxy listening on http://localhost:${port}`);
|
|
1400
|
+
console.log("Press Ctrl+C to stop all mocks.\n");
|
|
1401
|
+
});
|
|
1402
|
+
function shutdown() {
|
|
1403
|
+
console.log("\nShutting down...");
|
|
1404
|
+
proxy.close();
|
|
1405
|
+
for (const { server } of running) server.stop();
|
|
1406
|
+
process.exit(0);
|
|
1407
|
+
}
|
|
1408
|
+
process.on("SIGINT", shutdown);
|
|
1409
|
+
process.on("SIGTERM", shutdown);
|
|
1410
|
+
return new Promise(() => void 0);
|
|
1411
|
+
}
|
|
1412
|
+
//#endregion
|
|
1413
|
+
//#region src/commands/mock/validate.ts
|
|
1414
|
+
function pass(msg) {
|
|
1415
|
+
console.log(chalk.green(`\u2713 ${msg}`));
|
|
1416
|
+
}
|
|
1417
|
+
function fail(ctx, msg) {
|
|
1418
|
+
console.log(chalk.red(`\u2717 ${msg}`));
|
|
1419
|
+
ctx.failures++;
|
|
1420
|
+
}
|
|
1421
|
+
async function subdirs(dir) {
|
|
1422
|
+
const entries = await readdir(dir);
|
|
1423
|
+
const dirs = [];
|
|
1424
|
+
for (const entry of entries) if ((await stat(join(dir, entry))).isDirectory()) dirs.push(entry);
|
|
1425
|
+
return dirs.sort();
|
|
1426
|
+
}
|
|
1427
|
+
function checkStructure(ctx, entries, label) {
|
|
1428
|
+
if (entries.includes("README.md")) pass(`${label}: has README.md`);
|
|
1429
|
+
else fail(ctx, `${label}: missing README.md`);
|
|
1430
|
+
if (entries.includes("mock.json")) {
|
|
1431
|
+
pass(`${label}: has mock.json`);
|
|
1432
|
+
return true;
|
|
1433
|
+
}
|
|
1434
|
+
fail(ctx, `${label}: missing mock.json`);
|
|
1435
|
+
return false;
|
|
1436
|
+
}
|
|
1437
|
+
async function checkSchema(ctx, data, label) {
|
|
1438
|
+
const result = (await import("@mockoon/commons")).EnvironmentSchemaNoFix.validate(data, { abortEarly: false });
|
|
1439
|
+
if (!result.error) pass(`${label}: valid Mockoon schema`);
|
|
1440
|
+
else for (const detail of result.error.details) fail(ctx, `${label}: schema — ${detail.message}`);
|
|
1441
|
+
}
|
|
1442
|
+
function checkResponseQuality(ctx, data, label) {
|
|
1443
|
+
const routes = data.routes ?? [];
|
|
1444
|
+
for (const route of routes) for (const resp of route.responses ?? []) {
|
|
1445
|
+
const respLabel = `${label} \u2192 ${route.uuid}/${resp.uuid}`;
|
|
1446
|
+
if ((resp.headers ?? []).some((h) => h.key.toLowerCase() === "content-type")) pass(`${respLabel}: has Content-Type`);
|
|
1447
|
+
else fail(ctx, `${respLabel}: missing Content-Type header`);
|
|
1448
|
+
if (resp.label && resp.label.trim().length > 0) pass(`${respLabel}: has label "${resp.label}"`);
|
|
1449
|
+
else fail(ctx, `${respLabel}: missing or empty label`);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
function checkDatabucketRefs(ctx, data, label) {
|
|
1453
|
+
const bucketIds = new Set((data.data ?? []).map((d) => d.id));
|
|
1454
|
+
for (const route of data.routes ?? []) for (const resp of route.responses ?? []) if (resp.bodyType === "DATABUCKET") {
|
|
1455
|
+
const respLabel = `${label} \u2192 ${route.uuid}/${resp.uuid}`;
|
|
1456
|
+
if (resp.databucketID && bucketIds.has(resp.databucketID)) pass(`${respLabel}: databucket "${resp.databucketID}" exists`);
|
|
1457
|
+
else fail(ctx, `${respLabel}: databucketID "${resp.databucketID}" not found in data array`);
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
async function discoverAllScenarios(mocksDir) {
|
|
1461
|
+
const scenarios = [];
|
|
1462
|
+
const providers = await subdirs(mocksDir);
|
|
1463
|
+
for (const provider of providers) {
|
|
1464
|
+
const providerDir = join(mocksDir, provider);
|
|
1465
|
+
for (const scenario of await subdirs(providerDir)) scenarios.push(`${provider}/${scenario}`);
|
|
1466
|
+
}
|
|
1467
|
+
return scenarios;
|
|
1468
|
+
}
|
|
1469
|
+
function resolveScenarioDir(arg) {
|
|
1470
|
+
const abs = resolve(arg);
|
|
1471
|
+
if (basename(abs) === "mock.json") return dirname(abs);
|
|
1472
|
+
return abs;
|
|
1473
|
+
}
|
|
1474
|
+
async function validateScenario(ctx, scenarioDir, mocksDir) {
|
|
1475
|
+
const label = relative(mocksDir, scenarioDir);
|
|
1476
|
+
console.log(chalk.bold(`\n\u2500\u2500 ${label} \u2500\u2500`));
|
|
1477
|
+
let entries;
|
|
1478
|
+
try {
|
|
1479
|
+
entries = await readdir(scenarioDir);
|
|
1480
|
+
} catch {
|
|
1481
|
+
fail(ctx, `${label}: directory not found`);
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
if (!checkStructure(ctx, entries, label)) return;
|
|
1485
|
+
const mockPath = join(scenarioDir, "mock.json");
|
|
1486
|
+
let data;
|
|
1487
|
+
try {
|
|
1488
|
+
data = JSON.parse(await readFile(mockPath, "utf-8"));
|
|
1489
|
+
} catch (err) {
|
|
1490
|
+
fail(ctx, `${label}: invalid JSON \u2014 ${err instanceof Error ? err.message : String(err)}`);
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1493
|
+
await checkSchema(ctx, data, label);
|
|
1494
|
+
checkResponseQuality(ctx, data, label);
|
|
1495
|
+
checkDatabucketRefs(ctx, data, label);
|
|
1496
|
+
}
|
|
1497
|
+
async function runMockValidate(mocksRoot, paths) {
|
|
1498
|
+
const ctx = { failures: 0 };
|
|
1499
|
+
const mocksDir = resolve(mocksRoot);
|
|
1500
|
+
const targets = paths.length > 0 ? paths.map(resolveScenarioDir) : (await discoverAllScenarios(mocksDir)).map((s) => join(mocksDir, s));
|
|
1501
|
+
if (targets.length === 0) {
|
|
1502
|
+
fail(ctx, "No scenarios found under mocks/");
|
|
1503
|
+
return 1;
|
|
1504
|
+
}
|
|
1505
|
+
console.log(chalk.bold("\nValidating mock configs...\n"));
|
|
1506
|
+
for (const target of targets) await validateScenario(ctx, target, mocksDir);
|
|
1507
|
+
console.log(chalk.bold("\n── summary ──"));
|
|
1508
|
+
if (ctx.failures === 0) console.log(chalk.green("✓ All checks passed"));
|
|
1509
|
+
else console.log(chalk.red(`\u2717 ${ctx.failures} check(s) failed`));
|
|
1510
|
+
return ctx.failures === 0 ? 0 : 1;
|
|
1511
|
+
}
|
|
1512
|
+
const mockCommand = defineCommand({
|
|
1513
|
+
name: "mock",
|
|
1514
|
+
description: "Mock API server management",
|
|
1515
|
+
subCommands: {
|
|
1516
|
+
start: defineCommand({
|
|
1517
|
+
name: "start",
|
|
1518
|
+
description: "Start mock API servers with reverse proxy",
|
|
1519
|
+
args: z.object({
|
|
1520
|
+
mocksRoot: arg(z.string().default("./mocks"), { description: "Path to mocks directory" }),
|
|
1521
|
+
port: arg(z.coerce.number().default(3e3), {
|
|
1522
|
+
alias: "p",
|
|
1523
|
+
description: "Reverse proxy port"
|
|
1524
|
+
}),
|
|
1525
|
+
filter: arg(z.array(z.string()).default([]), {
|
|
1526
|
+
positional: true,
|
|
1527
|
+
description: "Filter by provider or provider/scenario"
|
|
1528
|
+
})
|
|
1529
|
+
}),
|
|
1530
|
+
run: async (args) => {
|
|
1531
|
+
const exitCode = await runMockStart(args.mocksRoot, args.filter, args.port);
|
|
1532
|
+
process.exit(exitCode);
|
|
1533
|
+
}
|
|
1534
|
+
}),
|
|
1535
|
+
validate: defineCommand({
|
|
1536
|
+
name: "validate",
|
|
1537
|
+
description: "Validate mock scenario configs",
|
|
1538
|
+
args: z.object({
|
|
1539
|
+
mocksRoot: arg(z.string().default("./mocks"), { description: "Path to mocks directory" }),
|
|
1540
|
+
paths: arg(z.array(z.string()).default([]), {
|
|
1541
|
+
positional: true,
|
|
1542
|
+
description: "Specific scenario paths to validate"
|
|
1543
|
+
})
|
|
1544
|
+
}),
|
|
1545
|
+
run: async (args) => {
|
|
1546
|
+
const exitCode = await runMockValidate(args.mocksRoot, args.paths);
|
|
1547
|
+
process.exit(exitCode);
|
|
1548
|
+
}
|
|
1549
|
+
})
|
|
1550
|
+
}
|
|
1551
|
+
});
|
|
1552
|
+
//#endregion
|
|
1553
|
+
//#region src/commands/index.ts
|
|
1554
|
+
const cwd = process.cwd();
|
|
1555
|
+
//#endregion
|
|
1556
|
+
//#region src/cli.ts
|
|
1557
|
+
runMain(defineCommand({
|
|
1558
|
+
name: "erp-kit",
|
|
1559
|
+
description: "ERP module framework CLI",
|
|
1560
|
+
subCommands: {
|
|
1561
|
+
module: moduleCommand,
|
|
1562
|
+
app: appCommand,
|
|
1563
|
+
mock: mockCommand,
|
|
1564
|
+
init: defineCommand({
|
|
1565
|
+
name: "init",
|
|
1566
|
+
description: "First-time setup for a consumer repo",
|
|
1567
|
+
run: () => {
|
|
1568
|
+
const exitCode = runInit(cwd);
|
|
1569
|
+
process.exit(exitCode);
|
|
1570
|
+
}
|
|
1571
|
+
}),
|
|
1572
|
+
update: defineCommand({
|
|
1573
|
+
name: "update",
|
|
1574
|
+
description: "Refresh framework-managed resources (skills, workflows)",
|
|
1575
|
+
args: z.object({ resources: arg(z.array(z.string()).default([]), {
|
|
1576
|
+
positional: true,
|
|
1577
|
+
description: "Resources to update (skills, workflows). Defaults to all."
|
|
1578
|
+
}) }),
|
|
1579
|
+
run: (args) => {
|
|
1580
|
+
const exitCode = runUpdate(cwd, args.resources);
|
|
1581
|
+
process.exit(exitCode);
|
|
1582
|
+
}
|
|
1583
|
+
}),
|
|
1584
|
+
license: defineCommand({
|
|
1585
|
+
name: "license",
|
|
1586
|
+
description: "License management",
|
|
1587
|
+
subCommands: {
|
|
1588
|
+
check: defineCommand({
|
|
1589
|
+
name: "check",
|
|
1590
|
+
description: "Check dependency licenses against allowlist",
|
|
1591
|
+
args: z.object({ config: arg(z.string(), {
|
|
1592
|
+
alias: "c",
|
|
1593
|
+
description: "Path to license config JSON file"
|
|
1594
|
+
}) }),
|
|
1595
|
+
run: (args) => {
|
|
1596
|
+
const exitCode = runLicenseCheck(args.config);
|
|
1597
|
+
process.exit(exitCode);
|
|
1598
|
+
}
|
|
1599
|
+
}),
|
|
1600
|
+
list: defineCommand({
|
|
1601
|
+
name: "list",
|
|
1602
|
+
description: "List available license groups",
|
|
1603
|
+
run: () => {
|
|
1604
|
+
const exitCode = runLicenseList();
|
|
1605
|
+
process.exit(exitCode);
|
|
1606
|
+
}
|
|
1607
|
+
})
|
|
1608
|
+
}
|
|
1609
|
+
})
|
|
1610
|
+
}
|
|
1611
|
+
}));
|
|
1612
|
+
//#endregion
|
|
1613
|
+
export {};
|