Transport package can update more than one folder?

I’m creating a Transport Package and I want to add snippets and chunks to various folders.

Is this possible?

A more concrete example of what you’re trying to accomplish would be useful, but speaking generally: yes you can package any number of directories in a single transport package.

Probably my code at fault then. I don’t get the Peoples directory or the peoples snippet when I install the package made by this:

<?php

/**

 * servicecenter build script

 *

 * @package servicecenter

 * @subpackage build

 */

$mtime = microtime();

$mtime = explode(" ", $mtime);

$mtime = $mtime[1] + $mtime[0];

$tstart = $mtime;

set_time_limit(0); /* makes sure our script doesnt timeout */

$root = dirname(dirname(__FILE__)) . '/';

$sources = array(

    'root' => $root,

    'build' => $root . 'build/',

    'resolvers' => $root . 'build/resolvers/',

    'data' => $root . 'build/data/',

    'source_core' => $root . 'core/components/servicecenter',

    'lexicon' => $root . 'core/components/servicecenter/lexicon/',

    'source_assets' => $root . 'assets/components/servicecenter',

    'docs' => $root . 'core/components/servicecenter/docs/',

);

unset($root); /* save memory */

require_once dirname(__FILE__) . '/build.config.php';

require_once MODX_CORE_PATH . 'model/modx/modx.class.php';

$modx = new modX();

$modx->initialize('mgr');

$modx->setLogLevel(modX::LOG_LEVEL_INFO);

$modx->setLogTarget(XPDO_CLI_MODE ? 'ECHO' : 'HTML');

$modx->loadClass('transport.modPackageBuilder', '', false, true);

$builder = new modPackageBuilder($modx);

$builder->createPackage('servicecenter', '1', 'rc1');

$builder->registerNamespace('servicecenter', false, true, '{core_path}components/servicecenter/');

/* add resources */

$hasResources = file_exists($sources['data'] . 'transport.resources.php');

if ($hasResources) {

    $resources = include $sources['data'] . 'transport.resources.php';

    if (!is_array($resources)) {

        die('Resources not an array');

    } else {

        $attributes = array(

            xPDOTransport::PRESERVE_KEYS => false,

            xPDOTransport::UPDATE_OBJECT => true,

            xPDOTransport::UNIQUE_KEY => 'pagetitle',

            xPDOTransport::RELATED_OBJECTS => true,

            xPDOTransport::RELATED_OBJECT_ATTRIBUTES => array(

                'ContentType' => array(

                    xPDOTransport::PRESERVE_KEYS => false,

                    xPDOTransport::UPDATE_OBJECT => true,

                    xPDOTransport::UNIQUE_KEY => 'name',

                ),

            ),

        );

        foreach ($resources as $resource) {

            $vehicle = $builder->createVehicle($resource, $attributes);

            $builder->putVehicle($vehicle);

        }

    }

    unset($resources, $resource, $attributes);

}

/* add root snippets */

$snippets = include $sources['data'] . 'transport.snippets.php';

foreach ($snippets as $snippet) {

    $vehicle = $builder->createVehicle($snippet, array(

        xPDOTransport::UNIQUE_KEY => 'name',

        xPDOTransport::UPDATE_OBJECT => true,

        xPDOTransport::PRESERVE_KEYS => false,

    ));

    $builder->putVehicle($vehicle);

}

/* create Peoples category */

$category = $modx->newObject('modCategory');

$category->set('id', 0);

$category->set('category', 'Peoples');

/* add Peoples snippet */

$snippet = include $sources['data'] . 'transport.people_snippets.php';

if (is_array($snippet)) {

    $category->addMany($snippet);

} else {

    $modx->log(modX::LOG_LEVEL_INFO, 'Adding snippet failed.');

}

/* create Peoples category vehicle */

$attr = array(

    xPDOTransport::UNIQUE_KEY => 'category',

    xPDOTransport::PRESERVE_KEYS => true,

    xPDOTransport::UPDATE_OBJECT => true,

    xPDOTransport::RELATED_OBJECTS => true,

    xPDOTransport::RELATED_OBJECT_ATTRIBUTES => array(

        'Snippets' => array(

            xPDOTransport::PRESERVE_KEYS => true,

            xPDOTransport::UPDATE_OBJECT => true,

            xPDOTransport::UNIQUE_KEY => 'name',

        ),

    )

);

$vehicle = $builder->createVehicle($category, $attr);

/* create PeopleGroup category */

$category = $modx->newObject('modCategory');

$category->set('id', 0);

$category->set('category', 'PeopleGroup');

/* add PeopleGroup chunks */

$chunks = include $sources['data'] . 'transport.chunks.php';

if (is_array($chunks)) {

    $category->addMany($chunks);

} else {

    $modx->log(modX::LOG_LEVEL_INFO, 'Adding chunks failed.');

}

/* create PeopleGroup category vehicle */

$attr = array(

    xPDOTransport::UNIQUE_KEY => 'category',

    xPDOTransport::PRESERVE_KEYS => true,

    xPDOTransport::UPDATE_OBJECT => true,

    xPDOTransport::RELATED_OBJECTS => true,

    xPDOTransport::RELATED_OBJECT_ATTRIBUTES => array(

        'Chunks' => array(

            xPDOTransport::PRESERVE_KEYS => true,

            xPDOTransport::UPDATE_OBJECT => true,

            xPDOTransport::UNIQUE_KEY => 'name',

        ),

    )

);

$vehicle = $builder->createVehicle($category, $attr);

$builder->putVehicle($vehicle);

$builder->pack();

$mtime = microtime();

$mtime = explode(" ", $mtime);

$mtime = $mtime[1] + $mtime[0];

$tend = $mtime;

$totalTime = ($tend - $tstart);

$totalTime = sprintf("%2.4f s", $totalTime);

$modx->log(modX::LOG_LEVEL_INFO, "\nPackage Built.\nExecution time: {$totalTime}\n");

session_write_close();

exit();

maybe, it would be easier, if you would use GPM to build your package
Home - Git Package Management (theboxer.github.io)

maybe use this system-settings, if you don’t want GPM alters your existing elements
Git-Package-Management/system-settings.md at master · theboxer/Git-Package-Management (github.com)
Git-Package-Management/system-settings.md at master · theboxer/Git-Package-Management (github.com)

depending on what you are trying to package, teleport might be another option
modxcms/teleport: Teleport is a packaging toolkit for MODX Revolution (github.com)

We are developing an application that will be used by 10 or more clients and I’m looking at options for moving updates from our installation to theirs. At the moment the updates are only Resources and Elements.

For the first steps it seems like using a basic Transport Package build script is the way to go because there isn’t any of the normal Extras stuff included. Just a simple readme.

In the future we might consider moving the development to static files and creating a package from those. One issue is we have multiple people working on the Resources etc so development would have to be cloud based.

I’m looking into GPM and MyComponent.

Adding snippets to the root snippet folder and a sub-folder seems to be working like this. Not sure what I was doing wrong before.

/* create Peoples category */
$categoryPeoples= $modx->newObject('modCategory');
$categoryPeoples->set('id', 0);
$categoryPeoples->set('category', 'Peoples');

/* add Peoples snippet */
$snippets = include $sources['data'] . 'transport.people_snippets.php';
if (is_array($snippets)) {
    $categoryPeoples->addMany($snippets);
} else {
    $modx->log(modX::LOG_LEVEL_INFO, 'Adding snippet failed.');
}

/* create Peoples category vehicle */
$attr = array(
    xPDOTransport::UNIQUE_KEY => 'category',
    xPDOTransport::PRESERVE_KEYS => false,
    xPDOTransport::UPDATE_OBJECT => true,
    xPDOTransport::RELATED_OBJECTS => true,
    xPDOTransport::RELATED_OBJECT_ATTRIBUTES => array(
        'Snippets' => array(
            xPDOTransport::PRESERVE_KEYS => false,
            xPDOTransport::UPDATE_OBJECT => true,
            xPDOTransport::UNIQUE_KEY => 'name',
        ),
    )
);
$vehicle = $builder->createVehicle($categoryPeoples, $attr);
$builder->putVehicle($vehicle);

/* add root snippets */
$snippets = include $sources['data'] . 'transport.snippets.php';
foreach ($snippets as $snippet) {
    $vehicle = $builder->createVehicle($snippet, array(
        xPDOTransport::UNIQUE_KEY => 'name',
        xPDOTransport::UPDATE_OBJECT => true,
        xPDOTransport::PRESERVE_KEYS => false,
    ));
    $builder->putVehicle($vehicle);
}

This is what Teleport was built for. Why would you want to write manual scripts to do this?

But I’ve decided to put the snippets in two sub-directories and this also seems to be working:

/* create Peoples category */
$categoryPeoples= $modx->newObject('modCategory');
$categoryPeoples->set('id', 0);
$categoryPeoples->set('category', 'Peoples');

/* add Peoples snippet */
$snippets = include $sources['data'] . 'transport.people_snippets.php';
if (is_array($snippets)) {
    $categoryPeoples->addMany($snippets);
} else {
    $modx->log(modX::LOG_LEVEL_INFO, 'Adding snippet failed.');
}

/* create Peoples category vehicle */
$attr = array(
    xPDOTransport::UNIQUE_KEY => 'category',
    xPDOTransport::PRESERVE_KEYS => false,
    xPDOTransport::UPDATE_OBJECT => true,
    xPDOTransport::RELATED_OBJECTS => true,
    xPDOTransport::RELATED_OBJECT_ATTRIBUTES => array(
        'Snippets' => array(
            xPDOTransport::PRESERVE_KEYS => false,
            xPDOTransport::UPDATE_OBJECT => true,
            xPDOTransport::UNIQUE_KEY => 'name',
        ),
    )
);
$vehicle = $builder->createVehicle($categoryPeoples, $attr);
$builder->putVehicle($vehicle);

/* create Hooks category */
$categoryHooks= $modx->newObject('modCategory');
$categoryHooks->set('id', 1);
$categoryHooks->set('category', 'Hooks');

/* add Hooks snippets */
$snippets = include $sources['data'] . 'transport.snippets.php';
if (is_array($snippets)) {
    $categoryHooks->addMany($snippets);
} else {
    $modx->log(modX::LOG_LEVEL_INFO, 'Adding snippet failed.');
}

/* create Hooks category vehicle */
$attr = array(
    xPDOTransport::UNIQUE_KEY => 'category',
    xPDOTransport::PRESERVE_KEYS => false,
    xPDOTransport::UPDATE_OBJECT => true,
    xPDOTransport::RELATED_OBJECTS => true,
    xPDOTransport::RELATED_OBJECT_ATTRIBUTES => array(
        'Snippets' => array(
            xPDOTransport::PRESERVE_KEYS => false,
            xPDOTransport::UPDATE_OBJECT => true,
            xPDOTransport::UNIQUE_KEY => 'name',
        ),
    )
);
$vehicle = $builder->createVehicle($categoryHooks, $attr);
$builder->putVehicle($vehicle);

Also looking into Teleport. I want to understand the options properly so I know the benefits and drawbacks of each one depending which route we take for further development of the application.

+1 for Teleport in that case

I have Teleport installed and it looks like something we will definitely be using in the future.

However, if we are making updates with a resource, a few snippets and several chunks then we still have to manually create lengthy JSON files for this? I’m not sure we want to use one of the included extraction templates because we would have to be totally sure that there wasn’t any test files that get swept up. We would have to keep a totally clean Dev server.

I wondered about programmatically generating the extract templates from a spreadsheet. Shouldn’t be too hard to make a template and fill in the values in a loop from a CSV.

FYI, MyComponent’s config file is a simple PHP array, and if you update resources, snippets, or chunks, it will automatically include them as long as they’re listed in the config. It you add elements, they’ll be included too as long as they’re in a category you’ve listed.

I’m looking at MyComponents.

At the moment all our Resources/Elements are in those tabs in the manager so in this situation we run ExportObjects and build?

Later if we move to developing in the file system we can use ImportObjects to move those files into the Manager and then ExportObjects again and then build to make the package?

Sorry, I don’t know what this means, but I think the answer is yes.

As mentioned by others, there are multiple solutions out there. Teleport is what is used behind the scenes for Modxcloud.com for their hosting service (from my understanding). I’ve used Modx Cloud off and on over the years but I’m more of a hobbyist and couldn’t justify the cost.

The coolest feature I thought was the ability to clone instances of Modx, and also create “Snapshots” to promote changes from one instance to another. It’s a great option if you need a “cloud based” deployment solution. If you’re building an App on Modx that has enough revenue to cover the hosting cost, which is minimal, I think Modx Cloud is a must.

  • Spin up an exact copy of any Modx instance and have a client test out features you’ve built without messing up your Dev instance.
  • Promote all developed changes, files, properties, etc. up to your Prod instance in 2 minutes or less.
  • Create point in time backups before applying branches of code, or working on a new feature.
  • Create a separate instance for a creative team to apply CSS and theme changes, then move those changes to another instance.

Anyway… just thought I’d throw that out there.


Also, the below extra has similarities to others mentioned but focuses more on a graphical interface in the Modx Manager. If you’re Elements, Snippets, etc. are all under one Category, this could be useful as it includes a “Transport Builder” feature.

I wrote the below extra to be more graphical and use the Manager interface to allow you to create a “package” schema to define custom tables if needed. It also includes a “Transport Builder” feature that will take your single top level category and all your scripts, etc. and create the transport package for you.

Even though you’re not creating an Extra, in my opinion, it’s still a good idea to use the structure of Extras and keep everything in /core/components/myapp/. Then also create a namespace and category to associate anything custom to your app such as any elements, settings, etc. Even if you’re not building something for the manager, still a good way to organize it all and link it together.

  • Current Limitations
    • Only supports a single Category that everything must be associated to

Disclaimer: I wrote this Extra, so I’m biased :slight_smile:.

https://modx.com/extras/package/extrabuilder

Jump to about 22:36 to see the Transport Builder specific details.

Is there some way to include selected custom tables in the Teleport package?

I found this

            "vehicle_package": "",
            "vehicle_class": "\\Teleport\\Transport\\MySQLVehicle",
            "object": {
                "classes": [
                    "modAccessAction",
                    "modAccessActionDom",
                   ....
                   "modUserMessage",
                    "modUserSetting",
                    "modWorkspace",
                    "registry.db.modDbRegisterMessage",
                    "sources.modMediaSourceContext"
                ],
                "excludeExtraTables": [],
                "excludeExtraTablePrefix": []
            },
            "attributes": {
                "vehicle_package": "",
                "vehicle_class": "\\Teleport\\Transport\\MySQLVehicle"
            }

but I want to include specific tables rather than exclude them.

Sussed it.

Use xPDOFileVehicle to move the component first. Something like this…

{ "vehicle_class": "xPDOFileVehicle", "object": { "source": "{+properties.modx.core_path}components/MyComponent", "target": "return MODX_CORE_PATH . \'components/\';" }, "attributes": {"vehicle_class": "xPDOFileVehicle"} } ';

Then use the xPDOScriptVehicle to pack a script in the Teleport package that creates the tables from the classes. Something like this:

{ "vehicle_class": "xPDOScriptVehicle", "object": { "source": "{+properties.modx.core_path}components/ev_teleport/create_tables.php", "classes": ["classnamehere"] }, "attributes": { "vehicle_class": "xPDOScriptVehicle" } } ';

where create_tables.php looks something like this:

    $componentClass = 'EcgdAppointment'; // $object['classes'];
    $packageName = 'ecgd'; //strtolower($componentClass);
    	
    $transport->xpdo->addPackage($packageName,MODX_CORE_PATH.'components/'.$packageName.'/model/','modx_');
    $manager = $transport->xpdo->getManager();
    $manager->createObjectContainer($componentClass);

So now I can paste my spreadsheet with all the elements I want to move into a form, submit it and it creates the extract template containing Resources, Elements and the database classes.

I’ll clean this up and try to make some documentation.