@wp-typia/project-tools 0.22.0 → 0.22.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/dist/runtime/built-in-block-code-templates/interactivity.d.ts +1 -1
- package/dist/runtime/built-in-block-code-templates/interactivity.js +2 -2
- package/dist/runtime/cli-add-workspace-admin-view-scaffold.d.ts +9 -0
- package/dist/runtime/cli-add-workspace-admin-view-scaffold.js +257 -0
- package/dist/runtime/cli-add-workspace-admin-view-source.d.ts +5 -0
- package/dist/runtime/cli-add-workspace-admin-view-source.js +86 -0
- package/dist/runtime/cli-add-workspace-admin-view-templates.d.ts +23 -0
- package/dist/runtime/cli-add-workspace-admin-view-templates.js +991 -0
- package/dist/runtime/cli-add-workspace-admin-view-types.d.ts +29 -0
- package/dist/runtime/cli-add-workspace-admin-view-types.js +29 -0
- package/dist/runtime/cli-add-workspace-admin-view.d.ts +1 -16
- package/dist/runtime/cli-add-workspace-admin-view.js +22 -1388
- package/dist/runtime/cli-add-workspace-ai-source-emitters.js +119 -1
- package/dist/runtime/cli-add-workspace-ai.js +253 -30
- package/dist/runtime/package-versions.d.ts +15 -0
- package/dist/runtime/package-versions.js +72 -42
- package/dist/runtime/scaffold-compatibility.d.ts +2 -0
- package/dist/runtime/scaffold-compatibility.js +2 -0
- package/dist/runtime/string-case.d.ts +7 -0
- package/dist/runtime/string-case.js +21 -11
- package/dist/runtime/typia-llm.d.ts +37 -2
- package/dist/runtime/typia-llm.js +240 -3
- package/dist/runtime/workspace-inventory.js +24 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare const INTERACTIVITY_EDIT_TEMPLATE = "import type { BlockEditProps } from '@wp-typia/block-types/blocks/registration';\nimport { __ } from '@wordpress/i18n';\nimport { useBlockProps, InspectorControls, RichText, BlockControls, AlignmentToolbar } from '@wordpress/block-editor';\nimport { PanelBody, RangeControl, Button, Notice } from '@wordpress/components';\nimport { useState } from '@wordpress/element';\nimport currentManifest from './manifest-document';\nimport { {{slugCamelCase}}Store } from './interactivity-store';\nimport {\n InspectorFromManifest,\n useEditorFields,\n useTypedAttributeUpdater,\n} from '@wp-typia/block-runtime/inspector';\nimport type { {{pascalCase}}Attributes } from './types';\nimport {\n sanitize{{pascalCase}}Attributes,\n validate{{pascalCase}}Attributes,\n} from './validators';\nimport { useTypiaValidation } from './hooks';\n\ntype EditProps = BlockEditProps<{{pascalCase}}Attributes>;\n\nconst actionButtonRowStyle = { display: 'flex', gap: '8px', marginTop: '16px' };\nconst validationListStyle = { margin: 0, paddingLeft: '1em' };\n\nexport default function Edit({ attributes, setAttributes, isSelected }: EditProps) {\n const [isPreviewing, setIsPreviewing] = useState(false);\n const editorFields = useEditorFields(currentManifest, {\n manual: ['content', 'clickCount', 'maxClicks'],\n labels: {\n alignment: __('Alignment', '{{textDomain}}'),\n animation: __('Animation', '{{textDomain}}'),\n interactiveMode: __('Interactive Mode', '{{textDomain}}'),\n isVisible: __('Visible', '{{textDomain}}'),\n showCounter: __('Show Counter', '{{textDomain}}'),\n },\n });\n const { errorMessages, isValid } = useTypiaValidation(\n attributes,\n validate{{pascalCase}}Attributes,\n );\n const validateEditorUpdate = (nextAttributes: {{pascalCase}}Attributes) => {\n try {\n return {\n data: sanitize{{pascalCase}}Attributes(nextAttributes),\n errors: [],\n isValid: true as const,\n };\n } catch {\n return validate{{pascalCase}}Attributes(nextAttributes);\n }\n };\n const { updateField } = useTypedAttributeUpdater(\n attributes,\n setAttributes,\n validateEditorUpdate\n );\n const alignmentValue = editorFields.getStringValue(\n attributes,\n 'alignment',\n 'left'\n ) as NonNullable<{{pascalCase}}Attributes['alignment']>;\n const clickCount = attributes.clickCount ?? 0;\n const isVisible = editorFields.getBooleanValue(\n attributes,\n 'isVisible',\n true\n );\n const isAnimating = attributes.isAnimating ?? false;\n const maxClicks = attributes.maxClicks ?? 0;\n const showCounter = editorFields.getBooleanValue(\n attributes,\n 'showCounter',\n true\n );\n const interactiveMode = editorFields.getStringValue(\n attributes,\n 'interactiveMode',\n 'click'\n ) as NonNullable<{{pascalCase}}Attributes['interactiveMode']>;\n const animation = editorFields.getStringValue(\n attributes,\n 'animation',\n 'none'\n ) as NonNullable<{{pascalCase}}Attributes['animation']>;\n\n const blockProps = useBlockProps({\n className: `{{cssClassName}} {{cssClassName}}--${interactiveMode}`,\n 'data-wp-interactive': {{slugCamelCase}}Store.directive.interactive,\n 'data-wp-context': JSON.stringify(\n {{slugCamelCase}}Store.createContext({\n clicks: clickCount,\n isAnimating,\n isVisible,\n animation,\n maxClicks,\n })\n )\n });\n const previewContentStyle = { textAlign: alignmentValue };\n const progressBarStyle = { width: `${(clickCount / maxClicks) * 100}%` };\n const clicksDirective = {{slugCamelCase}}Store.directive.state('clicks');\n const isAnimatingDirective = {{slugCamelCase}}Store.directive.state('isAnimating');\n const progressDirective = {{slugCamelCase}}Store.directive.state('progress') + \" + '%'\";\n\n const resetCounter = () => {\n updateField('clickCount', 0);\n updateField('isAnimating', false);\n };\n\n const testAnimation = () => {\n updateField('isAnimating', true);\n setTimeout(() => {\n updateField('isAnimating', false);\n }, 1000);\n };\n\n return (\n <>\n <BlockControls>\n <AlignmentToolbar\n value={alignmentValue}\n onChange={(value) => updateField('alignment', (value || alignmentValue) as NonNullable<{{pascalCase}}Attributes['alignment']>)}\n />\n </BlockControls>\n\n <InspectorControls>\n <InspectorFromManifest\n attributes={attributes}\n fieldLookup={editorFields}\n onChange={updateField}\n paths={['alignment', 'interactiveMode', 'animation', 'showCounter', 'isVisible']}\n title={__('Interactive Settings', '{{textDomain}}')}\n />\n\n <PanelBody title={__('Counter Settings', '{{textDomain}}')}>\n <RangeControl\n label={__('Max Clicks', '{{textDomain}}')}\n value={maxClicks}\n onChange={(value) => updateField('maxClicks', value ?? maxClicks)}\n min={0}\n max={100}\n help={__('Set to 0 for unlimited clicks', '{{textDomain}}')}\n />\n\n <div style={actionButtonRowStyle}>\n <Button\n variant=\"secondary\"\n onClick={resetCounter}\n isSmall\n >\n {__('Reset Counter', '{{textDomain}}')}\n </Button>\n <Button\n variant=\"secondary\"\n onClick={testAnimation}\n isSmall\n >\n {__('Test Animation', '{{textDomain}}')}\n </Button>\n </div>\n </PanelBody>\n\n {!isValid && (\n <PanelBody title={__('Validation Errors', '{{textDomain}}')} initialOpen>\n {errorMessages.map((error, index) => (\n <Notice key={index} status=\"error\" isDismissible={false}>\n {error}\n </Notice>\n ))}\n </PanelBody>\n )}\n\n {isSelected && (\n <PanelBody title={__('Preview', '{{textDomain}}')}>\n <Button\n variant={isPreviewing ? 'primary' : 'secondary'}\n onClick={() => setIsPreviewing(!isPreviewing)}\n aria-pressed={isPreviewing}\n isSmall\n >\n {isPreviewing\n ? __('Disable Preview Mode', '{{textDomain}}')\n : __('Enable Preview Mode', '{{textDomain}}')}\n </Button>\n\n {clickCount > 0 && (\n <Notice status=\"info\" isDismissible={false}>\n {__('Current clicks:', '{{textDomain}}')} {clickCount}\n {maxClicks > 0 && ` / ${maxClicks}`}\n </Notice>\n )}\n </PanelBody>\n )}\n </InspectorControls>\n\n <div {...blockProps}>\n <div\n className={`{{cssClassName}}__content ${isAnimating ? 'is-animating' : ''}`}\n style={previewContentStyle}\n data-wp-on--click={isPreviewing ? {{slugCamelCase}}Store.directive.action('handleClick') : undefined}\n data-wp-on--mouseenter={isPreviewing && interactiveMode === 'hover' ? {{slugCamelCase}}Store.directive.action('handleMouseEnter') : undefined}\n data-wp-on--mouseleave={isPreviewing && interactiveMode === 'hover' ? {{slugCamelCase}}Store.directive.action('handleMouseLeave') : undefined}\n >\n <RichText\n tagName=\"p\"\n value={attributes.content}\n onChange={(value) => updateField('content', value)}\n placeholder={__( {{titleJson}} + ' \u2013 click me to interact!', '{{textDomain}}')}\n />\n\n {!isValid && (\n <Notice status=\"error\" isDismissible={false}>\n <p>\n <strong>{__('Validation Errors', '{{textDomain}}')}</strong>\n </p>\n <ul style={validationListStyle}>\n {errorMessages.map((error, index) => (\n <li key={index}>{error}</li>\n ))}\n </ul>\n </Notice>\n )}\n\n {showCounter && (\n <div className=\"{{cssClassName}}__counter\">\n <span className=\"{{cssClassName}}__counter-label\">\n {__('Clicks:', '{{textDomain}}')}\n </span>\n <span\n className=\"{{cssClassName}}__counter-value\"\n data-wp-text={clicksDirective}\n >\n {clickCount}\n </span>\n </div>\n )}\n\n {maxClicks > 0 && (\n <div className=\"{{cssClassName}}__progress\">\n <div\n className=\"{{cssClassName}}__progress-bar\"\n style={progressBarStyle}\n data-wp-style--width={progressDirective}\n />\n </div>\n )}\n\n {animation !== 'none' && (\n <div\n className={`{{cssClassName}}__animation ${isAnimating ? 'is-active' : ''}`}\n data-wp-class--is-active={isAnimatingDirective}\n >\n {animation}\n </div>\n )}\n </div>\n </div>\n </>\n );\n}\n";
|
|
2
2
|
export declare const INTERACTIVITY_SAVE_TEMPLATE = "import { useBlockProps, RichText } from '@wordpress/block-editor';\nimport { __ } from '@wordpress/i18n';\nimport { {{slugCamelCase}}Store } from './interactivity-store';\nimport type { {{pascalCase}}Attributes } from './types';\n\nexport default function Save({ attributes }: { attributes: {{pascalCase}}Attributes }) {\n const clickCount = attributes.clickCount ?? 0;\n const interactiveMode = attributes.interactiveMode ?? 'click';\n const animation = attributes.animation ?? 'none';\n const isAnimating = attributes.isAnimating ?? false;\n const isVisible = attributes.isVisible ?? true;\n const maxClicks = attributes.maxClicks ?? 0;\n const showCounter = attributes.showCounter ?? true;\n const contentStyle = { textAlign: attributes.alignment };\n const clickActionDirective = {{slugCamelCase}}Store.directive.action('handleClick');\n const visibilityHiddenDirective = {{slugCamelCase}}Store.directive.negate(\n {{slugCamelCase}}Store.directive.state('isVisible')\n );\n const clicksDirective = {{slugCamelCase}}Store.directive.state('clicks');\n const clampedClicksDirective = {{slugCamelCase}}Store.directive.state('clampedClicks');\n const isAnimatingDirective = {{slugCamelCase}}Store.directive.state('isAnimating');\n const completionHiddenDirective = {{slugCamelCase}}Store.directive.negate(\n {{slugCamelCase}}Store.directive.state('isComplete')\n );\n const resetActionDirective = {{slugCamelCase}}Store.directive.action('reset');\n const blockProps = useBlockProps.save({\n className: `{{cssClassName}} {{cssClassName}}--${interactiveMode}`,\n 'data-wp-interactive': {{slugCamelCase}}Store.directive.interactive,\n 'data-wp-context': JSON.stringify(\n {{slugCamelCase}}Store.createContext({\n clicks: clickCount,\n isAnimating,\n isVisible,\n animation,\n maxClicks,\n })\n )\n });\n const progressDirective = {{slugCamelCase}}Store.directive.state('progress') + \" + '%'\";\n\n return (\n <div {...blockProps}>\n <div\n className={`{{cssClassName}}__content ${isAnimating ? 'is-animating' : ''}`}\n style={contentStyle}\n data-wp-on--click={clickActionDirective}\n data-wp-on--mouseenter={interactiveMode === 'hover' ? {{slugCamelCase}}Store.directive.action('handleMouseEnter') : undefined}\n data-wp-on--mouseleave={interactiveMode === 'hover' ? {{slugCamelCase}}Store.directive.action('handleMouseLeave') : undefined}\n data-wp-bind--hidden={visibilityHiddenDirective}\n >\n <RichText.Content\n tagName=\"p\"\n value={attributes.content}\n className=\"{{cssClassName}}__text\"\n />\n\n {showCounter && (\n <div\n className=\"{{cssClassName}}__counter\"\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n >\n <span className=\"{{cssClassName}}__counter-label\">\n { __( 'Clicks:', '{{textDomain}}' ) }\n </span>\n <span\n className=\"{{cssClassName}}__counter-value\"\n data-wp-text={clicksDirective}\n >\n {clickCount}\n </span>\n </div>\n )}\n\n {maxClicks > 0 && (\n <div className=\"{{cssClassName}}__progress\">\n <div\n className=\"{{cssClassName}}__progress-bar\"\n role=\"progressbar\"\n aria-label={ __( 'Click progress', '{{textDomain}}' ) }\n aria-valuemin={0}\n aria-valuemax={maxClicks}\n aria-valuenow={Math.min(clickCount, maxClicks)}\n data-wp-bind--aria-valuenow={clampedClicksDirective}\n data-wp-style--width={progressDirective}\n />\n </div>\n )}\n\n <div\n className={`{{cssClassName}}__animation ${animation}`}\n aria-hidden=\"true\"\n data-wp-class--is-active={isAnimatingDirective}\n />\n\n {maxClicks > 0 && (\n <div\n className=\"{{cssClassName}}__completion\"\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n data-wp-bind--hidden={completionHiddenDirective}\n >\n { __( '\uD83C\uDF89 Complete!', '{{textDomain}}' ) }\n </div>\n )}\n\n <button\n className=\"{{cssClassName}}__reset\"\n data-wp-on--click={resetActionDirective}\n aria-label={ __( 'Reset counter', '{{textDomain}}' ) }\n >\n <span aria-hidden=\"true\">\u21BB</span>\n <span className=\"screen-reader-text\">\n { __( 'Reset counter', '{{textDomain}}' ) }\n </span>\n </button>\n </div>\n </div>\n );\n}\n";
|
|
3
3
|
export declare const INTERACTIVITY_INDEX_TEMPLATE = "import {\n registerScaffoldBlockType,\n type BlockConfiguration,\n} from '@wp-typia/block-types/blocks/registration';\nimport type { BlockSupports } from '@wp-typia/block-types/blocks/supports';\nimport {\n buildScaffoldBlockRegistration,\n parseScaffoldBlockMetadata,\n} from '@wp-typia/block-runtime/blocks';\n\nimport Edit from './edit';\nimport Save from './save';\nimport metadata from './block-metadata';\nimport './editor.scss';\nimport './style.scss';\n\nimport type { {{pascalCase}}Attributes } from './types';\n\nconst scaffoldSupports = {\n html: false,\n align: true,\n anchor: true,\n className: true,\n interactivity: true,\n} satisfies BlockSupports;\n\nconst registration = buildScaffoldBlockRegistration(\n parseScaffoldBlockMetadata<BlockConfiguration<{{pascalCase}}Attributes>>(metadata),\n {\n supports: scaffoldSupports,\n edit: Edit,\n save: Save,\n }\n);\n\nregisterScaffoldBlockType(registration.name, registration.settings);\n";
|
|
4
|
-
export declare const INTERACTIVITY_STORE_TEMPLATE = "import type {\n {{pascalCase}}Context,\n {{pascalCase}}State,\n} from './types';\n\ntype InteractivityActionShape = object;\ntype InteractivityCallbackShape = object;\ntype InteractivityContextShape = object;\ntype InteractivityStateShape = object;\ntype InteractivityCallable =
|
|
4
|
+
export declare const INTERACTIVITY_STORE_TEMPLATE = "import type {\n {{pascalCase}}Context,\n {{pascalCase}}State,\n} from './types';\n\ntype InteractivityActionShape = object;\ntype InteractivityCallbackShape = object;\ntype InteractivityContextShape = object;\ntype InteractivityStateShape = object;\ntype InteractivityCallable = CallableFunction;\ntype InteractivityKey<T extends object> = Extract<keyof T, string>;\ntype InteractivityMethodKey<T extends object> = {\n [Key in InteractivityKey<T>]: T[Key] extends InteractivityCallable ? Key : never;\n}[InteractivityKey<T>];\n\ntype InteractivityDirectivePath<\n Root extends string,\n Key extends string,\n> = `${Root}.${Key}`;\n\ntype NegatedInteractivityDirectivePath<Path extends string> = `!${Path}`;\n\nexport interface TypedInteractivityDirectiveHelpers<\n State extends InteractivityStateShape,\n Context extends InteractivityContextShape,\n Actions extends InteractivityActionShape,\n Callbacks extends InteractivityCallbackShape,\n Namespace extends string,\n> {\n readonly interactive: Namespace;\n action<Key extends InteractivityMethodKey<Actions>>(\n key: Key,\n ): InteractivityDirectivePath<'actions', Key>;\n callback<Key extends InteractivityMethodKey<Callbacks>>(\n key: Key,\n ): InteractivityDirectivePath<'callbacks', Key>;\n state<Key extends InteractivityKey<State>>(\n key: Key,\n ): InteractivityDirectivePath<'state', Key>;\n context<Key extends InteractivityKey<Context>>(\n key: Key,\n ): InteractivityDirectivePath<'context', Key>;\n negate<Path extends string>(\n path: Path,\n ): NegatedInteractivityDirectivePath<Path>;\n}\n\nexport interface TypedInteractivityStore<\n Namespace extends string,\n State extends InteractivityStateShape,\n Context extends InteractivityContextShape,\n Actions extends InteractivityActionShape,\n Callbacks extends InteractivityCallbackShape,\n> {\n readonly namespace: Namespace;\n readonly state: State;\n readonly context: Context;\n readonly actions: Actions;\n readonly callbacks: Callbacks;\n readonly directive: TypedInteractivityDirectiveHelpers<\n State,\n Context,\n Actions,\n Callbacks,\n Namespace\n >;\n createContext(value: Context): Context;\n}\n\nexport function defineInteractivityStore<\n Namespace extends string,\n State extends InteractivityStateShape,\n Context extends InteractivityContextShape,\n Actions extends InteractivityActionShape,\n Callbacks extends InteractivityCallbackShape,\n>(config: {\n readonly namespace: Namespace;\n readonly state: State;\n readonly context: Context;\n readonly actions: Actions;\n readonly callbacks: Callbacks;\n}): TypedInteractivityStore<Namespace, State, Context, Actions, Callbacks> {\n return {\n namespace: config.namespace,\n state: config.state,\n context: config.context,\n actions: config.actions,\n callbacks: config.callbacks,\n directive: {\n interactive: config.namespace,\n action<Key extends InteractivityMethodKey<Actions>>(key: Key) {\n return `actions.${key}` as InteractivityDirectivePath<'actions', Key>;\n },\n callback<Key extends InteractivityMethodKey<Callbacks>>(key: Key) {\n return `callbacks.${key}` as InteractivityDirectivePath<'callbacks', Key>;\n },\n state<Key extends InteractivityKey<State>>(key: Key) {\n return `state.${key}` as InteractivityDirectivePath<'state', Key>;\n },\n context<Key extends InteractivityKey<Context>>(key: Key) {\n return `context.${key}` as InteractivityDirectivePath<'context', Key>;\n },\n negate<Path extends string>(path: Path) {\n return `!${path}` as NegatedInteractivityDirectivePath<Path>;\n },\n },\n createContext(value) {\n return value;\n },\n };\n}\n\ntype InteractivityActionHandler = CallableFunction;\n\nexport interface {{pascalCase}}StoreActions {\n handleClick: InteractivityActionHandler;\n handleMouseEnter: InteractivityActionHandler;\n handleMouseLeave: InteractivityActionHandler;\n reset: InteractivityActionHandler;\n}\n\nexport interface {{pascalCase}}StoreCallbacks {}\n\nexport const {{slugCamelCase}}Store = defineInteractivityStore({\n namespace: '{{slugKebabCase}}',\n state: {} as {{pascalCase}}State,\n context: {} as {{pascalCase}}Context,\n actions: {} as {{pascalCase}}StoreActions,\n callbacks: {} as {{pascalCase}}StoreCallbacks,\n});\n";
|
|
5
5
|
export declare const INTERACTIVITY_SCRIPT_TEMPLATE = "/**\n * WordPress Interactivity API implementation for {{title}} block\n */\nimport { store, getContext, getElement, withSyncEvent } from '@wordpress/interactivity';\nimport {\n {{slugCamelCase}}Store,\n type {{pascalCase}}StoreActions,\n} from './interactivity-store';\nimport type { {{pascalCase}}Context, {{pascalCase}}State } from './types';\n\nfunction getBlockContext() {\n return getContext<{{pascalCase}}Context>();\n}\n\nconst actions: {{pascalCase}}StoreActions = {\n // Handle block click\n handleClick: () => {\n const context = getBlockContext();\n const { ref } = getElement();\n\n if (!ref) {\n return;\n }\n\n if (context.maxClicks > 0 && context.clicks >= context.maxClicks) {\n return;\n }\n\n const previousClicks = context.clicks;\n\n // Increment click counter\n context.clicks += 1;\n\n // Trigger animation\n if (context.animation !== 'none') {\n context.isAnimating = true;\n setTimeout(() => {\n context.isAnimating = false;\n }, 1000);\n }\n\n // Emit custom event\n ref.dispatchEvent(new CustomEvent('{{slugKebabCase}}:click', {\n detail: { clicks: context.clicks }\n }));\n\n // Check if max clicks reached\n if (context.maxClicks > 0 && previousClicks < context.maxClicks && context.clicks === context.maxClicks) {\n ref.dispatchEvent(new CustomEvent('{{slugKebabCase}}:complete', {\n detail: { totalClicks: context.clicks }\n }));\n }\n },\n\n // Handle hover events\n handleMouseEnter: () => {\n const context = getBlockContext();\n if (context.animation === 'none') return;\n context.isAnimating = true;\n },\n\n handleMouseLeave: () => {\n const context = getBlockContext();\n if (context.animation === 'none') return;\n context.isAnimating = false;\n },\n\n // Reset counter\n reset: withSyncEvent((event: Event) => {\n event.stopPropagation();\n const context = getBlockContext();\n context.clicks = 0;\n context.isAnimating = false;\n })\n};\n\nconst state = {\n get clicks() {\n return getBlockContext().clicks;\n },\n get isAnimating() {\n return getBlockContext().isAnimating;\n },\n get isVisible() {\n return getBlockContext().isVisible;\n },\n get progress() {\n const context = getBlockContext();\n const clampedClicks =\n context.maxClicks > 0\n ? Math.min(context.clicks, context.maxClicks)\n : context.clicks;\n return context.maxClicks > 0 ? (clampedClicks / context.maxClicks) * 100 : 0;\n },\n get clampedClicks() {\n const context = getBlockContext();\n return context.maxClicks > 0\n ? Math.min(context.clicks, context.maxClicks)\n : context.clicks;\n },\n get isComplete() {\n const context = getBlockContext();\n return context.clicks >= context.maxClicks && context.maxClicks > 0;\n }\n} satisfies {{pascalCase}}State;\n\n// Store configuration\nstore({{slugCamelCase}}Store.namespace, {\n // State - reactive data that updates the UI\n state,\n actions,\n callbacks: {{slugCamelCase}}Store.callbacks,\n});\n";
|
|
6
6
|
export declare const INTERACTIVITY_VALIDATORS_TEMPLATE = "import typia from 'typia';\nimport currentManifest from \"./manifest-defaults-document\";\nimport { {{pascalCase}}Attributes, {{pascalCase}}ValidationResult } from \"./types\";\nimport { createTemplateValidatorToolkit } from \"./validator-toolkit\";\n\nconst scaffoldValidators = createTemplateValidatorToolkit<{{pascalCase}}Attributes>({\n assert: typia.createAssert<{{pascalCase}}Attributes>(),\n clone: typia.misc.createClone<{{pascalCase}}Attributes>() as (\n value: {{pascalCase}}Attributes,\n ) => {{pascalCase}}Attributes,\n is: typia.createIs<{{pascalCase}}Attributes>(),\n manifest: currentManifest,\n prune: typia.misc.createPrune<{{pascalCase}}Attributes>(),\n random: typia.createRandom<{{pascalCase}}Attributes>() as (\n ...args: unknown[]\n ) => {{pascalCase}}Attributes,\n validate: typia.createValidate<{{pascalCase}}Attributes>(),\n});\n\nexport const validate{{pascalCase}}Attributes =\n scaffoldValidators.validateAttributes as (\n attributes: unknown,\n ) => {{pascalCase}}ValidationResult;\n\nexport const validators = scaffoldValidators.validators;\n\nexport const sanitize{{pascalCase}}Attributes =\n scaffoldValidators.sanitizeAttributes as (\n attributes: Partial<{{pascalCase}}Attributes>,\n ) => {{pascalCase}}Attributes;\n\n/**\n * Runtime type guard for checking if an object is {{pascalCase}}Attributes.\n */\nexport const is{{pascalCase}}Attributes = (obj: unknown): obj is {{pascalCase}}Attributes => {\n return validators.is(obj);\n};\n\nexport const createAttributeUpdater = scaffoldValidators.createAttributeUpdater;\n";
|
|
@@ -428,7 +428,7 @@ type InteractivityActionShape = object;
|
|
|
428
428
|
type InteractivityCallbackShape = object;
|
|
429
429
|
type InteractivityContextShape = object;
|
|
430
430
|
type InteractivityStateShape = object;
|
|
431
|
-
type InteractivityCallable =
|
|
431
|
+
type InteractivityCallable = CallableFunction;
|
|
432
432
|
type InteractivityKey<T extends object> = Extract<keyof T, string>;
|
|
433
433
|
type InteractivityMethodKey<T extends object> = {
|
|
434
434
|
[Key in InteractivityKey<T>]: T[Key] extends InteractivityCallable ? Key : never;
|
|
@@ -531,7 +531,7 @@ export function defineInteractivityStore<
|
|
|
531
531
|
};
|
|
532
532
|
}
|
|
533
533
|
|
|
534
|
-
type InteractivityActionHandler =
|
|
534
|
+
type InteractivityActionHandler = CallableFunction;
|
|
535
535
|
|
|
536
536
|
export interface {{pascalCase}}StoreActions {
|
|
537
537
|
handleClick: InteractivityActionHandler;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { WorkspaceProject } from './workspace-project.js';
|
|
2
|
+
import { type AdminViewCoreDataSource, type AdminViewRestResource, type AdminViewSource } from './cli-add-workspace-admin-view-types.js';
|
|
3
|
+
export declare function scaffoldAdminViewWorkspace(options: {
|
|
4
|
+
adminViewSlug: string;
|
|
5
|
+
coreDataSource?: AdminViewCoreDataSource;
|
|
6
|
+
parsedSource?: AdminViewSource;
|
|
7
|
+
restResource?: AdminViewRestResource;
|
|
8
|
+
workspace: WorkspaceProject;
|
|
9
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { promises as fsp } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { appendWorkspaceInventoryEntries, readWorkspaceInventory, } from './workspace-inventory.js';
|
|
5
|
+
import { buildAdminViewConfigEntry, buildAdminViewConfigSource, buildAdminViewEntrySource, buildAdminViewPhpSource, buildAdminViewRegistrySource, buildAdminViewScreenSource, buildAdminViewStyleSource, buildAdminViewTypesSource, buildCoreDataAdminViewDataSource, buildCoreDataAdminViewScreenSource, buildDefaultAdminViewDataSource, buildRestAdminViewDataSource, } from './cli-add-workspace-admin-view-templates.js';
|
|
6
|
+
import { ADMIN_VIEWS_PHP_GLOB, isAdminViewCoreDataSource, } from './cli-add-workspace-admin-view-types.js';
|
|
7
|
+
import { getWorkspaceBootstrapPath, patchFile, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from './cli-add-shared.js';
|
|
8
|
+
import { DEFAULT_WORDPRESS_CORE_DATA_VERSION, DEFAULT_WORDPRESS_DATA_VERSION, DEFAULT_WORDPRESS_DATAVIEWS_VERSION, DEFAULT_WP_TYPIA_DATAVIEWS_VERSION, resolveManagedPackageVersionRange, } from './package-versions.js';
|
|
9
|
+
import { findPhpFunctionRange, hasPhpFunctionDefinition, replacePhpFunctionDefinition, } from './php-utils.js';
|
|
10
|
+
function detectJsonIndent(source) {
|
|
11
|
+
const indentMatch = /\n([ \t]+)"/u.exec(source);
|
|
12
|
+
return indentMatch?.[1] ?? 2;
|
|
13
|
+
}
|
|
14
|
+
async function ensureAdminViewPackageDependencies(workspace, adminViewSource) {
|
|
15
|
+
const packageJsonPath = path.join(workspace.projectDir, 'package.json');
|
|
16
|
+
const wpTypiaDataViewsVersion = resolveManagedPackageVersionRange({
|
|
17
|
+
fallback: DEFAULT_WP_TYPIA_DATAVIEWS_VERSION,
|
|
18
|
+
packageName: '@wp-typia/dataviews',
|
|
19
|
+
workspacePackageDirName: 'wp-typia-dataviews',
|
|
20
|
+
});
|
|
21
|
+
const wordpressDataViewsVersion = resolveManagedPackageVersionRange({
|
|
22
|
+
fallback: DEFAULT_WORDPRESS_DATAVIEWS_VERSION,
|
|
23
|
+
packageName: '@wordpress/dataviews',
|
|
24
|
+
});
|
|
25
|
+
const wordpressCoreDataVersion = resolveManagedPackageVersionRange({
|
|
26
|
+
fallback: DEFAULT_WORDPRESS_CORE_DATA_VERSION,
|
|
27
|
+
packageName: '@wordpress/core-data',
|
|
28
|
+
});
|
|
29
|
+
const wordpressDataVersion = resolveManagedPackageVersionRange({
|
|
30
|
+
fallback: DEFAULT_WORDPRESS_DATA_VERSION,
|
|
31
|
+
packageName: '@wordpress/data',
|
|
32
|
+
});
|
|
33
|
+
await patchFile(packageJsonPath, (source) => {
|
|
34
|
+
const packageJson = JSON.parse(source);
|
|
35
|
+
const coreDataDependencies = isAdminViewCoreDataSource(adminViewSource)
|
|
36
|
+
? {
|
|
37
|
+
'@wordpress/core-data': packageJson.dependencies?.['@wordpress/core-data'] ??
|
|
38
|
+
wordpressCoreDataVersion,
|
|
39
|
+
'@wordpress/data': packageJson.dependencies?.['@wordpress/data'] ??
|
|
40
|
+
wordpressDataVersion,
|
|
41
|
+
}
|
|
42
|
+
: {};
|
|
43
|
+
const nextDependencies = {
|
|
44
|
+
...(packageJson.dependencies ?? {}),
|
|
45
|
+
'@wordpress/dataviews': packageJson.dependencies?.['@wordpress/dataviews'] ??
|
|
46
|
+
wordpressDataViewsVersion,
|
|
47
|
+
...coreDataDependencies,
|
|
48
|
+
};
|
|
49
|
+
const nextDevDependencies = {
|
|
50
|
+
...(packageJson.devDependencies ?? {}),
|
|
51
|
+
'@wp-typia/dataviews': packageJson.devDependencies?.['@wp-typia/dataviews'] ??
|
|
52
|
+
wpTypiaDataViewsVersion,
|
|
53
|
+
};
|
|
54
|
+
if (JSON.stringify(nextDependencies) ===
|
|
55
|
+
JSON.stringify(packageJson.dependencies ?? {}) &&
|
|
56
|
+
JSON.stringify(nextDevDependencies) ===
|
|
57
|
+
JSON.stringify(packageJson.devDependencies ?? {})) {
|
|
58
|
+
return source;
|
|
59
|
+
}
|
|
60
|
+
packageJson.dependencies = nextDependencies;
|
|
61
|
+
packageJson.devDependencies = nextDevDependencies;
|
|
62
|
+
return `${JSON.stringify(packageJson, null, detectJsonIndent(source))}\n`;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
async function ensureAdminViewBootstrapAnchors(workspace) {
|
|
66
|
+
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
67
|
+
await patchFile(bootstrapPath, (source) => {
|
|
68
|
+
let nextSource = source;
|
|
69
|
+
const loadFunctionName = `${workspace.workspace.phpPrefix}_load_admin_views`;
|
|
70
|
+
const loadHook = `add_action( 'plugins_loaded', '${loadFunctionName}' );`;
|
|
71
|
+
const loadHookPattern = new RegExp(`add_action\\(\\s*['"]plugins_loaded['"]\\s*,\\s*['"]${loadFunctionName}['"]\\s*\\)\\s*;`, 'u');
|
|
72
|
+
const loadFunction = `
|
|
73
|
+
|
|
74
|
+
function ${loadFunctionName}() {
|
|
75
|
+
\tforeach ( glob( __DIR__ . '${ADMIN_VIEWS_PHP_GLOB}' ) ?: array() as $admin_view_module ) {
|
|
76
|
+
\t\trequire_once $admin_view_module;
|
|
77
|
+
\t}
|
|
78
|
+
}
|
|
79
|
+
`;
|
|
80
|
+
const insertionAnchors = [
|
|
81
|
+
/add_action\(\s*["']init["']\s*,\s*["'][^"']+_load_textdomain["']\s*\);\s*\n/u,
|
|
82
|
+
/\?>\s*$/u,
|
|
83
|
+
];
|
|
84
|
+
const insertPhpSnippet = (snippet) => {
|
|
85
|
+
for (const anchor of insertionAnchors) {
|
|
86
|
+
const candidate = nextSource.replace(anchor, (match) => `${snippet}\n${match}`);
|
|
87
|
+
if (candidate !== nextSource) {
|
|
88
|
+
nextSource = candidate;
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
nextSource = `${nextSource.trimEnd()}\n${snippet}\n`;
|
|
93
|
+
};
|
|
94
|
+
const appendPhpSnippet = (snippet) => {
|
|
95
|
+
const closingTagPattern = /\?>\s*$/u;
|
|
96
|
+
if (closingTagPattern.test(nextSource)) {
|
|
97
|
+
nextSource = nextSource.replace(closingTagPattern, `${snippet}\n?>`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
nextSource = `${nextSource.trimEnd()}\n${snippet}\n`;
|
|
101
|
+
};
|
|
102
|
+
if (!hasPhpFunctionDefinition(nextSource, loadFunctionName)) {
|
|
103
|
+
insertPhpSnippet(loadFunction);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
const functionRange = findPhpFunctionRange(nextSource, loadFunctionName);
|
|
107
|
+
const functionSource = functionRange
|
|
108
|
+
? nextSource.slice(functionRange.start, functionRange.end)
|
|
109
|
+
: '';
|
|
110
|
+
if (!functionSource.includes(ADMIN_VIEWS_PHP_GLOB)) {
|
|
111
|
+
const replacedSource = replacePhpFunctionDefinition(nextSource, loadFunctionName, loadFunction);
|
|
112
|
+
if (!replacedSource) {
|
|
113
|
+
throw new Error(`Unable to repair ${path.basename(bootstrapPath)} for ${loadFunctionName}.`);
|
|
114
|
+
}
|
|
115
|
+
nextSource = replacedSource;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (!loadHookPattern.test(nextSource)) {
|
|
119
|
+
appendPhpSnippet(loadHook);
|
|
120
|
+
}
|
|
121
|
+
return nextSource;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
async function ensureAdminViewBuildScriptAnchors(workspace) {
|
|
125
|
+
const buildScriptPath = path.join(workspace.projectDir, 'scripts', 'build-workspace.mjs');
|
|
126
|
+
await patchFile(buildScriptPath, (source) => {
|
|
127
|
+
if (/['"]src\/admin-views\/index\.(?:ts|js)['"]/u.test(source)) {
|
|
128
|
+
return source;
|
|
129
|
+
}
|
|
130
|
+
const currentSharedEntriesPattern = /(\r?\n\s*['"]src\/editor-plugins\/index\.js['"])\s*,?/u;
|
|
131
|
+
let nextSource = source.replace(currentSharedEntriesPattern, `$1,
|
|
132
|
+
\t\t'src/admin-views/index.ts',
|
|
133
|
+
\t\t'src/admin-views/index.js',`);
|
|
134
|
+
if (nextSource !== source) {
|
|
135
|
+
return nextSource;
|
|
136
|
+
}
|
|
137
|
+
const legacySharedEntriesPattern = /\[\s*['"]src\/bindings\/index\.ts['"]\s*,\s*['"]src\/bindings\/index\.js['"]\s*(?:,\s*)?\]/u;
|
|
138
|
+
nextSource = source.replace(legacySharedEntriesPattern, `[
|
|
139
|
+
\t\t'src/bindings/index.ts',
|
|
140
|
+
\t\t'src/bindings/index.js',
|
|
141
|
+
\t\t'src/editor-plugins/index.ts',
|
|
142
|
+
\t\t'src/editor-plugins/index.js',
|
|
143
|
+
\t\t'src/admin-views/index.ts',
|
|
144
|
+
\t\t'src/admin-views/index.js',
|
|
145
|
+
\t]`);
|
|
146
|
+
if (nextSource !== source) {
|
|
147
|
+
return nextSource;
|
|
148
|
+
}
|
|
149
|
+
throw new Error(`Unable to update ${path.relative(workspace.projectDir, buildScriptPath)} for admin view shared entries.`);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
async function ensureAdminViewWebpackAnchors(workspace) {
|
|
153
|
+
const webpackConfigPath = path.join(workspace.projectDir, 'webpack.config.js');
|
|
154
|
+
await patchFile(webpackConfigPath, (source) => {
|
|
155
|
+
if (/['"]admin-views\/index['"]/u.test(source)) {
|
|
156
|
+
return source;
|
|
157
|
+
}
|
|
158
|
+
const editorPluginEntryPattern = /(\n\s*\[\s*['"]editor-plugins\/index['"][\s\S]*?['"]src\/editor-plugins\/index\.js['"][\s\S]*?\]\s*\])\s*,?/u;
|
|
159
|
+
let nextSource = source.replace(editorPluginEntryPattern, `$1,
|
|
160
|
+
\t\t[
|
|
161
|
+
\t\t\t'admin-views/index',
|
|
162
|
+
\t\t\t[ 'src/admin-views/index.ts', 'src/admin-views/index.js' ],
|
|
163
|
+
\t\t],`);
|
|
164
|
+
if (nextSource !== source) {
|
|
165
|
+
return nextSource;
|
|
166
|
+
}
|
|
167
|
+
const legacySharedEntriesBlockPattern = /for\s*\(\s*const\s+relativePath\s+of\s+\[\s*['"]src\/bindings\/index\.ts['"]\s*,\s*['"]src\/bindings\/index\.js['"]\s*(?:,\s*)?\]\s*\)\s*\{[\s\S]*?entries\.push\(\s*\[\s*['"]bindings\/index['"]\s*,\s*entryPath\s*\]\s*\);\s*break;\s*\}/u;
|
|
168
|
+
const nextSharedEntriesBlock = `\tfor ( const [ entryName, candidates ] of [\n\t\t[\n\t\t\t'bindings/index',\n\t\t\t[ 'src/bindings/index.ts', 'src/bindings/index.js' ],\n\t\t],\n\t\t[\n\t\t\t'editor-plugins/index',\n\t\t\t[ 'src/editor-plugins/index.ts', 'src/editor-plugins/index.js' ],\n\t\t],\n\t\t[\n\t\t\t'admin-views/index',\n\t\t\t[ 'src/admin-views/index.ts', 'src/admin-views/index.js' ],\n\t\t],\n\t] ) {\n\t\tfor ( const relativePath of candidates ) {\n\t\t\tconst entryPath = path.resolve( process.cwd(), relativePath );\n\t\t\tif ( ! fs.existsSync( entryPath ) ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tentries.push( [ entryName, entryPath ] );\n\t\t\tbreak;\n\t\t}\n\t}`;
|
|
169
|
+
nextSource = source.replace(legacySharedEntriesBlockPattern, nextSharedEntriesBlock);
|
|
170
|
+
if (nextSource === source) {
|
|
171
|
+
throw new Error(`Unable to update ${path.relative(workspace.projectDir, webpackConfigPath)} for admin view shared entries.`);
|
|
172
|
+
}
|
|
173
|
+
return nextSource;
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
function resolveAdminViewRegistryPath(projectDir) {
|
|
177
|
+
const adminViewsDir = path.join(projectDir, 'src', 'admin-views');
|
|
178
|
+
return ([
|
|
179
|
+
path.join(adminViewsDir, 'index.ts'),
|
|
180
|
+
path.join(adminViewsDir, 'index.js'),
|
|
181
|
+
].find((candidatePath) => fs.existsSync(candidatePath)) ??
|
|
182
|
+
path.join(adminViewsDir, 'index.ts'));
|
|
183
|
+
}
|
|
184
|
+
function readAdminViewRegistrySlugs(registryPath) {
|
|
185
|
+
if (!fs.existsSync(registryPath)) {
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
const source = fs.readFileSync(registryPath, 'utf8');
|
|
189
|
+
return Array.from(source.matchAll(/^\s*import\s+['"]\.\/([^/'"]+)(?:\/index(?:\.[cm]?[jt]sx?)?)?['"];?\s*$/gmu)).map((match) => match[1]);
|
|
190
|
+
}
|
|
191
|
+
async function writeAdminViewRegistry(projectDir, adminViewSlug) {
|
|
192
|
+
const adminViewsDir = path.join(projectDir, 'src', 'admin-views');
|
|
193
|
+
const registryPath = resolveAdminViewRegistryPath(projectDir);
|
|
194
|
+
await fsp.mkdir(adminViewsDir, { recursive: true });
|
|
195
|
+
const existingAdminViewSlugs = readWorkspaceInventory(projectDir).adminViews.map((entry) => entry.slug);
|
|
196
|
+
const existingRegistrySlugs = readAdminViewRegistrySlugs(registryPath);
|
|
197
|
+
const nextAdminViewSlugs = Array.from(new Set([
|
|
198
|
+
...existingAdminViewSlugs,
|
|
199
|
+
...existingRegistrySlugs,
|
|
200
|
+
adminViewSlug,
|
|
201
|
+
])).sort();
|
|
202
|
+
await fsp.writeFile(registryPath, buildAdminViewRegistrySource(nextAdminViewSlugs), 'utf8');
|
|
203
|
+
}
|
|
204
|
+
export async function scaffoldAdminViewWorkspace(options) {
|
|
205
|
+
const { adminViewSlug, coreDataSource, parsedSource, restResource, workspace, } = options;
|
|
206
|
+
const blockConfigPath = path.join(workspace.projectDir, 'scripts', 'block-config.ts');
|
|
207
|
+
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
208
|
+
const buildScriptPath = path.join(workspace.projectDir, 'scripts', 'build-workspace.mjs');
|
|
209
|
+
const packageJsonPath = path.join(workspace.projectDir, 'package.json');
|
|
210
|
+
const webpackConfigPath = path.join(workspace.projectDir, 'webpack.config.js');
|
|
211
|
+
const adminViewsIndexPath = resolveAdminViewRegistryPath(workspace.projectDir);
|
|
212
|
+
const adminViewDir = path.join(workspace.projectDir, 'src', 'admin-views', adminViewSlug);
|
|
213
|
+
const adminViewPhpPath = path.join(workspace.projectDir, 'inc', 'admin-views', `${adminViewSlug}.php`);
|
|
214
|
+
const mutationSnapshot = {
|
|
215
|
+
fileSources: await snapshotWorkspaceFiles([
|
|
216
|
+
adminViewsIndexPath,
|
|
217
|
+
blockConfigPath,
|
|
218
|
+
bootstrapPath,
|
|
219
|
+
buildScriptPath,
|
|
220
|
+
packageJsonPath,
|
|
221
|
+
webpackConfigPath,
|
|
222
|
+
]),
|
|
223
|
+
snapshotDirs: [],
|
|
224
|
+
targetPaths: [adminViewDir, adminViewPhpPath],
|
|
225
|
+
};
|
|
226
|
+
try {
|
|
227
|
+
await fsp.mkdir(adminViewDir, { recursive: true });
|
|
228
|
+
await fsp.mkdir(path.dirname(adminViewPhpPath), { recursive: true });
|
|
229
|
+
await ensureAdminViewPackageDependencies(workspace, parsedSource);
|
|
230
|
+
await ensureAdminViewBootstrapAnchors(workspace);
|
|
231
|
+
await ensureAdminViewBuildScriptAnchors(workspace);
|
|
232
|
+
await ensureAdminViewWebpackAnchors(workspace);
|
|
233
|
+
await fsp.writeFile(path.join(adminViewDir, 'types.ts'), buildAdminViewTypesSource(adminViewSlug, restResource, coreDataSource), 'utf8');
|
|
234
|
+
await fsp.writeFile(path.join(adminViewDir, 'config.ts'), buildAdminViewConfigSource(adminViewSlug, workspace.workspace.textDomain, parsedSource, restResource), 'utf8');
|
|
235
|
+
await fsp.writeFile(path.join(adminViewDir, 'data.ts'), coreDataSource
|
|
236
|
+
? buildCoreDataAdminViewDataSource(adminViewSlug, coreDataSource)
|
|
237
|
+
: restResource
|
|
238
|
+
? buildRestAdminViewDataSource(adminViewSlug, restResource)
|
|
239
|
+
: buildDefaultAdminViewDataSource(adminViewSlug), 'utf8');
|
|
240
|
+
await fsp.writeFile(path.join(adminViewDir, 'Screen.tsx'), coreDataSource
|
|
241
|
+
? buildCoreDataAdminViewScreenSource(adminViewSlug, workspace.workspace.textDomain)
|
|
242
|
+
: buildAdminViewScreenSource(adminViewSlug, workspace.workspace.textDomain), 'utf8');
|
|
243
|
+
await fsp.writeFile(path.join(adminViewDir, 'index.tsx'), buildAdminViewEntrySource(adminViewSlug), 'utf8');
|
|
244
|
+
await fsp.writeFile(path.join(adminViewDir, 'style.scss'), buildAdminViewStyleSource(), 'utf8');
|
|
245
|
+
await fsp.writeFile(adminViewPhpPath, buildAdminViewPhpSource(adminViewSlug, workspace), 'utf8');
|
|
246
|
+
await writeAdminViewRegistry(workspace.projectDir, adminViewSlug);
|
|
247
|
+
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
248
|
+
adminViewEntries: [
|
|
249
|
+
buildAdminViewConfigEntry(adminViewSlug, parsedSource),
|
|
250
|
+
],
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
await rollbackWorkspaceMutation(mutationSnapshot);
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type AdminViewRestResource, type AdminViewSource } from './cli-add-workspace-admin-view-types.js';
|
|
2
|
+
export declare function assertAdminViewPackageAvailability(): void;
|
|
3
|
+
export declare function parseAdminViewSource(source?: string): AdminViewSource | undefined;
|
|
4
|
+
export declare function resolveRestResourceSource(restResources: AdminViewRestResource[], source: AdminViewSource | undefined): AdminViewRestResource | undefined;
|
|
5
|
+
export declare function resolveAdminViewCoreDataSource(source: AdminViewSource | undefined): import("./cli-add-workspace-admin-view-types.js").AdminViewCoreDataSource | undefined;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { assertValidGeneratedSlug } from './cli-add-shared.js';
|
|
2
|
+
import { CLI_DIAGNOSTIC_CODES, createCliDiagnosticCodeError, } from './cli-diagnostics.js';
|
|
3
|
+
import { ADMIN_VIEW_ALLOW_UNPUBLISHED_DATAVIEWS_ENV, ADMIN_VIEW_CORE_DATA_ENTITY_KIND_IDS, ADMIN_VIEW_CORE_DATA_ENTITY_NAME_PATTERN, ADMIN_VIEW_CORE_DATA_ENTITY_SEGMENT_PATTERN, ADMIN_VIEW_CORE_DATA_SOURCE_KIND, ADMIN_VIEW_PUBLIC_INSTALLS_ENABLED, ADMIN_VIEW_REST_SOURCE_KIND, ADMIN_VIEW_SOURCE_USAGE, isAdminViewCoreDataSource, isAdminViewRestResourceSource, } from './cli-add-workspace-admin-view-types.js';
|
|
4
|
+
function isAdminViewUnpublishedDataViewsOverrideEnabled() {
|
|
5
|
+
return (process.env[ADMIN_VIEW_ALLOW_UNPUBLISHED_DATAVIEWS_ENV]?.trim() === '1');
|
|
6
|
+
}
|
|
7
|
+
export function assertAdminViewPackageAvailability() {
|
|
8
|
+
if (isAdminViewUnpublishedDataViewsOverrideEnabled() ||
|
|
9
|
+
ADMIN_VIEW_PUBLIC_INSTALLS_ENABLED) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
throw createCliDiagnosticCodeError(CLI_DIAGNOSTIC_CODES.INVALID_ARGUMENT, '`wp-typia add admin-view` is temporarily unavailable because `@wp-typia/dataviews` is not published to npm for public installs yet.');
|
|
13
|
+
}
|
|
14
|
+
function assertValidCoreDataEntitySegment(label, value) {
|
|
15
|
+
const trimmed = value.trim();
|
|
16
|
+
if (!trimmed) {
|
|
17
|
+
throw new Error(`${label} is required. Use \`${ADMIN_VIEW_SOURCE_USAGE}\`.`);
|
|
18
|
+
}
|
|
19
|
+
if (!ADMIN_VIEW_CORE_DATA_ENTITY_SEGMENT_PATTERN.test(trimmed)) {
|
|
20
|
+
throw new Error(`${label} must start with a letter and contain only letters, numbers, underscores, or hyphens.`);
|
|
21
|
+
}
|
|
22
|
+
return trimmed;
|
|
23
|
+
}
|
|
24
|
+
function assertValidCoreDataEntityName(value) {
|
|
25
|
+
const normalized = value.trim();
|
|
26
|
+
if (!normalized) {
|
|
27
|
+
throw new Error(`Admin view source entity name is required. Use \`${ADMIN_VIEW_SOURCE_USAGE}\`.`);
|
|
28
|
+
}
|
|
29
|
+
if (!ADMIN_VIEW_CORE_DATA_ENTITY_NAME_PATTERN.test(normalized)) {
|
|
30
|
+
throw new Error('Admin view source entity name must start with a lowercase letter or number and contain only lowercase letters, numbers, underscores, or hyphens.');
|
|
31
|
+
}
|
|
32
|
+
return normalized;
|
|
33
|
+
}
|
|
34
|
+
function assertValidCoreDataEntityKind(value) {
|
|
35
|
+
const normalized = assertValidCoreDataEntitySegment('Admin view source entity kind', value);
|
|
36
|
+
if (!ADMIN_VIEW_CORE_DATA_ENTITY_KIND_IDS.includes(normalized)) {
|
|
37
|
+
throw new Error(`Admin view core-data sources currently support only: ${ADMIN_VIEW_CORE_DATA_ENTITY_KIND_IDS.join(', ')}.`);
|
|
38
|
+
}
|
|
39
|
+
return normalized;
|
|
40
|
+
}
|
|
41
|
+
export function parseAdminViewSource(source) {
|
|
42
|
+
const trimmed = source?.trim();
|
|
43
|
+
if (!trimmed) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
const separatorIndex = trimmed.indexOf(':');
|
|
47
|
+
const kind = separatorIndex === -1 ? trimmed : trimmed.slice(0, separatorIndex);
|
|
48
|
+
const locator = separatorIndex === -1 ? '' : trimmed.slice(separatorIndex + 1);
|
|
49
|
+
if (!locator) {
|
|
50
|
+
throw new Error('Admin view source must use `rest-resource:<slug>` or `core-data:<kind>/<name>`.');
|
|
51
|
+
}
|
|
52
|
+
if (kind === ADMIN_VIEW_REST_SOURCE_KIND) {
|
|
53
|
+
return {
|
|
54
|
+
kind,
|
|
55
|
+
slug: assertValidGeneratedSlug('Admin view source slug', locator, ADMIN_VIEW_SOURCE_USAGE),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (kind === ADMIN_VIEW_CORE_DATA_SOURCE_KIND) {
|
|
59
|
+
const [entityKind, entityName, extra] = locator.split('/');
|
|
60
|
+
if (!entityKind || !entityName || extra !== undefined) {
|
|
61
|
+
throw new Error('Admin view core-data sources must use `core-data:<kind>/<name>`, for example `core-data:postType/post`.');
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
entityKind: assertValidCoreDataEntityKind(entityKind),
|
|
65
|
+
entityName: assertValidCoreDataEntityName(entityName),
|
|
66
|
+
kind,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
throw new Error('Admin view source must use `rest-resource:<slug>` or `core-data:<kind>/<name>`.');
|
|
70
|
+
}
|
|
71
|
+
export function resolveRestResourceSource(restResources, source) {
|
|
72
|
+
if (!isAdminViewRestResourceSource(source)) {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
const restResource = restResources.find((entry) => entry.slug === source.slug);
|
|
76
|
+
if (!restResource) {
|
|
77
|
+
throw new Error(`Unknown REST resource source "${source.slug}". Choose one of: ${restResources.map((entry) => entry.slug).join(', ') || '<none>'}.`);
|
|
78
|
+
}
|
|
79
|
+
if (!restResource.methods.includes('list')) {
|
|
80
|
+
throw new Error(`REST resource source "${source.slug}" must include the list method for DataViews pagination.`);
|
|
81
|
+
}
|
|
82
|
+
return restResource;
|
|
83
|
+
}
|
|
84
|
+
export function resolveAdminViewCoreDataSource(source) {
|
|
85
|
+
return isAdminViewCoreDataSource(source) ? source : undefined;
|
|
86
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type WorkspaceProject } from './workspace-project.js';
|
|
2
|
+
import { type AdminViewCoreDataSource, type AdminViewRestResource, type AdminViewSource } from './cli-add-workspace-admin-view-types.js';
|
|
3
|
+
export declare function buildAdminViewConfigEntry(adminViewSlug: string, source: AdminViewSource | undefined): string;
|
|
4
|
+
export declare function buildAdminViewRegistrySource(adminViewSlugs: string[]): string;
|
|
5
|
+
/**
|
|
6
|
+
* Build the generated admin-view item and dataset types for the selected source.
|
|
7
|
+
*/
|
|
8
|
+
export declare function buildAdminViewTypesSource(adminViewSlug: string, restResource: AdminViewRestResource | undefined, coreDataSource: AdminViewCoreDataSource | undefined): string;
|
|
9
|
+
/**
|
|
10
|
+
* Build the generated DataViews config source for an admin-view scaffold.
|
|
11
|
+
*/
|
|
12
|
+
export declare function buildAdminViewConfigSource(adminViewSlug: string, textDomain: string, source: AdminViewSource | undefined, restResource: AdminViewRestResource | undefined): string;
|
|
13
|
+
export declare function buildDefaultAdminViewDataSource(adminViewSlug: string): string;
|
|
14
|
+
export declare function buildRestAdminViewDataSource(adminViewSlug: string, restResource: AdminViewRestResource): string;
|
|
15
|
+
/**
|
|
16
|
+
* Build a core-data-backed admin-view data module for a supported entity family.
|
|
17
|
+
*/
|
|
18
|
+
export declare function buildCoreDataAdminViewDataSource(adminViewSlug: string, coreDataSource: AdminViewCoreDataSource): string;
|
|
19
|
+
export declare function buildAdminViewScreenSource(adminViewSlug: string, textDomain: string): string;
|
|
20
|
+
export declare function buildCoreDataAdminViewScreenSource(adminViewSlug: string, textDomain: string): string;
|
|
21
|
+
export declare function buildAdminViewEntrySource(adminViewSlug: string): string;
|
|
22
|
+
export declare function buildAdminViewStyleSource(): string;
|
|
23
|
+
export declare function buildAdminViewPhpSource(adminViewSlug: string, workspace: WorkspaceProject): string;
|