Giter Club home page Giter Club logo

Comments (6)

Nicholas-Westby avatar Nicholas-Westby commented on July 22, 2024 1

@tomfulton Perfect! I went ahead and just used a custom manifest and it seems to be working exactly as I'd hope: rhythmagency/formulate@1402b54

Some clarification in the documentation would definitely go a long way to help developers understand that this is already possible.

from grunt-umbraco-package.

abjerner avatar abjerner commented on July 22, 2024

I think the difficult part here is determining a proper format for specifying the package actions, since it might be a bit complex.

grunt-umbraco-package internally uses js2xmlparser for transforming the manifest from JSON to XML. With a few lines of code added to grunt-umbraco-package, we can use the same syntax to specify the package actions.

Below an example of two different package actions:

umbracoPackage: {
    release: {
        src: 'files/',
        dest: './',
        options: {
            name: pkg.name,
            version: version,
            url: pkg.url,
            license: pkg.license.name,
            licenseUrl: pkg.license.url,
            author: pkg.author.name,
            authorUrl: pkg.author.url,
            readme: pkg.readme,
            outputName: pkg.name + '.v' + version + '.zip',
            actions: [
                {
                    '@': {
                        runat: 'uninstall',
                        undo: 'true',
                        alias: 'UmbracoFileSystemProviders.Azure.TransformConfig',
                        file: '~/web.config',
                        xdtfile: '~/App_Plugins/UmbracoFileSystemProviders/Azure/Install/web.config'
                    }
                },
                {
                    '@': {
                        runat: 'install',
                        undo: 'true',
                        alias: 'addDashboardSection',
                        dashboardAlias: 'MyDashboard'
                    },
                    section: {
                        areas: {
                            area: 'content'
                        },
                        tab: {
                            '@': {
                                caption: 'My tab'
                            },
                            control: '/App_Plugins/MyDashboard/MyTab.html'
                        }
                    }
                }
            ]
        }
    }
}

Properties of the @ object represents XML attributes, while other properties represents child XML elements.

The first action would generate the following XML:

<Action runat="uninstall" undo="true" alias="UmbracoFileSystemProviders.Azure.TransformConfig" file="~/web.config" xdtfile="~/App_Plug
ins/UmbracoFileSystemProviders/Azure/Install/web.config"/>

The second action would generate the following XML:

<Action runat="install" undo="true" alias="addDashboardSection" dashboardAlias="MyDashboard">
    <section>
        <areas>
            <area>content</area>
        </areas>
        <tab caption="My tab">
            <control>/App_Plugins/MyDashboard/MyTab.html</control>
        </tab>
    </section>
</Action>

@tomfulton What do you say about this? If the syntax seems fine and not overly complex, I can make a pull request to add support for this ;)

from grunt-umbraco-package.

Nicholas-Westby avatar Nicholas-Westby commented on July 22, 2024

That would work for me, but might be a bit overly complicated (unless I'm missing something, which I very well might). Currently, the readme can be specified with a string value. Why not do the same for package actions? The native "fs" Node.js module could be used to read in the contents of an XML file, then that could be passed to actions property.

umbracoPackage: {
    release: {
        src: 'files/',
        dest: './',
        options: {
            name: pkg.name,
            version: version,
            url: pkg.url,
            license: pkg.license.name,
            licenseUrl: pkg.license.url,
            author: pkg.author.name,
            authorUrl: pkg.author.url,
            readme: pkg.readme,
            outputName: pkg.name + '.v' + version + '.zip',
            actions: fs.readFileSync("sample.xml", {encoding: "utf8"});
        }
    }
}

Does js2xmlparser allow you to inject a string as XML (i.e., rather than having to specify the entire hierarchy in JSON)?

It seems like the most common scenario will be that package actions will start as XML. Any conversion to JSON will probably be a manual process (i.e., the developer would be converting from XML to JSON so that js2xmlparser could then convert it back to XML).

from grunt-umbraco-package.

tomfulton avatar tomfulton commented on July 22, 2024

Hi @Nicholas-Westby, thanks for reporting! I've been wondering how we might achieve the same for Document Types and the like, and wonder if this might be the time to tie all of these in.

Before getting too far, I should mention that what you're after is possible now, by using your own package.xml with the actions predefined, instead of letting the task generate it for you. You can do this by specifying the manifest option - check these for an example: Gruntfile.js, package.xml. I'll get the README updated to explain this better.

Regarding supporting this with the package.xml generation method though, @abjerner echoed my thoughts exactly - the trick is figuring out a format to specify them that makes sense. js2xmlparser seems pretty powerful, but yeah, the OOTB configuration seems a little verbose.

I think your idea of specifying an XML string for each of the "entities" packages support might be a good option. It does seem a bit strange though, since we're really trying to automate the XML generation with the latest update. But, with the exception of Actions, I'm guessing most users would use the Umbraco Backoffice to manually generate the XML first anyway, in which case maybe it makes sense to just let them copy/paste it...

@abjerner regarding your suggestion, I think something like this would be good for Actions (ideally if we could remove that second level '@': { ... } for each item though). But, I'm not sure this is the right solution for the other entities that Packages support, like DictionaryItems, Documents, etc etc, and maybe it makes sense to use the same solution for all? A quick look at the XSLTSearch package.xml makes me think that this approach could get messy quick:

  <Documents>
    <DocumentSet importMode="root">
      <XSLTsearch id="1090" parentID="-1" level="1" writerID="0" creatorID="0" nodeType="1087" template="1086" sortOrder="39" createDate="2010-11-09T13:45:22" updateDate="2010-11-09T14:18:04" nodeName="Search" urlName="search" writerName="Administrator" creatorName="Administrator" path="-1,1090" isDoc="">
        <umbracoNaviHide>0</umbracoNaviHide>
      </XSLTsearch>
    </DocumentSet>
  </Documents>
  <DocumentTypes>
    <DocumentType>
      <Info>
        <Name>XSLTsearch</Name>
        <Alias>XSLTsearch</Alias>
        <Icon>.sprTreeDoc2</Icon>
        <Thumbnail>doc.png</Thumbnail>
        <Description>XSLTsearch page.
(adjust settings via the macro in the XSLTsearch template)</Description>
        <AllowedTemplates>
          <Template>XSLTsearch</Template>
        </AllowedTemplates>
        <DefaultTemplate>XSLTsearch</DefaultTemplate>
      </Info>
      <Structure />
      <GenericProperties>
        <GenericProperty>
          <Name>Hide page?</Name>
          <Alias>umbracoNaviHide</Alias>
          <Type>38b352c1-e9f8-4fd8-9324-9a2eab06d97a</Type>
          <Definition>92897bc6-a5f3-4ffe-ae27-f2e7e33dda49</Definition>
          <Tab>
          </Tab>
          <Mandatory>False</Mandatory>
          <Validation>
          </Validation>
          <Description><![CDATA[]]></Description>
        </GenericProperty>
      </GenericProperties>
      <Tabs />
    </DocumentType>
  </DocumentTypes>

It also seems that some entities use different casing, for example, the Macro elements are all camelCased. We could probably overcome all of this with some configuration of js2xmlparser, but I wonder if it'd be easier for the developer to just use the XML instead of manually converting it to JSON?

What do you think about Nicholas' suggestion of using strings? Something like this?

umbracoPackage: {
    release: {
        src: 'files/',
        dest: './',
        options: {
            name: pkg.name,
            version: version,
            url: pkg.url,
            license: pkg.license.name,
            licenseUrl: pkg.license.url,
            author: pkg.author.name,
            authorUrl: pkg.author.url,
            readme: pkg.readme,
            outputName: pkg.name + '.v' + version + '.zip',
            actions: grunt.file.read('/config/package/actions.xml'),
            documents: grunt.file.read('/config/package/documents.xml'),
            dictionaryItems: grunt.file.read('/config/package/dictionaryItems.xml'),
            /// ... etc
        }
    }
}

Or if we want to keep things even simpler, maybe one field for all the XML?

options: {
    name: pkg.name,
    version: version,
    url: pkg.url,
    license: pkg.license.name,
    licenseUrl: pkg.license.url,
    author: pkg.author.name,
    authorUrl: pkg.author.url,
    readme: pkg.readme,
    additionalXml: grunt.file.read('/config/package/umbracoPackageXml.xml') // Any <Actions>, <DocumentTypes>, etc
}

I'm open to either approach (or other ideas), curious to hear what you all think makes sense! 👍

from grunt-umbraco-package.

abjerner avatar abjerner commented on July 22, 2024

We can check the type of options.actions when running the task, making it up to the developer what approach to use:

JSON
If the type is an array, it could work similar to what I posted earlier - the specified package actions are added to the JSON object representing the manifest before it is converted to XML. If we are to specify the package actions with JSON, I don't really think we can do much about the format when still using js2xmlparser, since the XML is in a format (attributes and child elements) unknown to the task. So we could call this the advanced approach.

For anything other than package actions, we can use a simpler JSON format like the example below, and then handle the rest in the task (proper casing of the XML elements and similar):

documentTypes: [
  info: {
    name: 'XSLTsearch',
    alias: 'XSLTsearch',
    icon: '.sprTreeDoc2',
    thumbnail: 'doc.png',
    description: 'XSLTsearch page. (adjust settings via the macro in the XSLTsearch template)',
    allowedTemplates: ['XSLTsearch'],
    defaultTemplate: 'XSLTsearch'
  },
  genericProperties: [
    {
      name: 'Hide page?',
      alias: 'umbracoNaviHide',
      type: '38b352c1-e9f8-4fd8-9324-9a2eab06d97a',
      definition: '92897bc6-a5f3-4ffe-ae27-f2e7e33dda49',
      mandatory: false
    }
  ]
]

The above is just a quick example. I have omitted a few fields here and there, but the task could just fill it the blanks. Again this could be considered the advanced approach.

XML
If the type is a string (XML), it becomes a little tricky to parse the XML into JSON, and then back to XML again.

However when the manifest is converted to an XML string, we can do a simple string replacement by replacing <Actions/> with the XML string specified in the options. This will mess up the indentation a bit, but otherwise seems to work fine. Then this would just work like @Nicholas-Westby suggested in his example. So this could be the simple approach.

Changes to the task
The change to allow package actions being specified with JSON just takes three lines of code at the right location:

if (Array.isArray(options.actions) && options.actions.length > 0) {
    data.Actions.Action = options.actions;
}

The code necessary is also very straight forward (and can be used for document types and similar as well). Currently we're just saving the generated XML directly to the package.xml file (see here), but if we store the XML in a variable first, the code for the string replacement could look like:

if (typeof(options.actions) == 'string') {
    if (options.actions.indexOf('<Actions') >= 0) {
        xmlManifest = xmlManifest.replace('<Actions/>', options.actions);
    }
}

and for document types like:

if (typeof(options.documentTypes) == 'string') {
    if (options.documentTypes.indexOf('<DocumentTypes') >= 0) {
        xmlManifest = xmlManifest.replace('<DocumentTypes/>', options.documentTypes);
    }
}

I hope this makes sense. Otherwise I can make a pull request, and you can play around with it ;)

from grunt-umbraco-package.

tomfulton avatar tomfulton commented on July 22, 2024

Hey @ajberner - Great idea, I love the approach of allowing the new option(s) to accept strings or JSON, and just injecting the XML with a string replacement.

I also like your thought about using the simpler syntax for the JSON (except for Actions). At first thought it does seem like this may be a lot of work though - handling every attribute a package can have, especially when I see things like this and think about what it looks like with more/nested content:

  <Documents>
    <DocumentSet importMode="root">
      <XSLTsearch id="1090" parentID="-1" level="1" writerID="0" creatorID="0" nodeType="1087" template="1086" sortOrder="39" createDate="2010-11-09T13:45:22" updateDate="2010-11-09T14:18:04" nodeName="Search" urlName="search" writerName="Administrator" creatorName="Administrator" path="-1,1090" isDoc="">
        <umbracoNaviHide>0</umbracoNaviHide>
      </XSLTsearch>
    </DocumentSet>
  </Documents>

...but maybe you already have an idea of how to handle this. Personally I think I would use the "simple"/XML approach if I had a complex package.xml to work with. But I'm happy to support the JSON method too if it'd be useful for you. If the simple syntax is too much work, I would be fine using your original suggestion of the js2xmlparser syntax too.

Lastly, I wonder if it makes sense to bundle all of these into one option to help keep the # of options manageable. For something like XSLTSearch, I imagine it might be cleaner to point something like optionsXml to a single file containing all of the entities, rather than having to split into 5 different files. Not a big deal, just thinking out loud here :)

Anyway, if you're bored I would gladly accept a PR, otherwise I will look to get this added over the next couple weeks.

Thanks!
Tom

from grunt-umbraco-package.

Related Issues (7)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.