Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/contentstack-bootstrap/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"test:report": "nyc --reporter=lcov mocha \"test/**/*.test.js\""
},
"dependencies": {
"@contentstack/cli-cm-seed": "~2.0.0-beta.9",
"@contentstack/cli-cm-seed": "~2.0.0-beta.10",
"@contentstack/cli-command": "~2.0.0-beta",
"@contentstack/cli-utilities": "~2.0.0-beta.1",
"@contentstack/cli-config": "~2.0.0-beta.2",
Expand Down
4 changes: 2 additions & 2 deletions packages/contentstack-bootstrap/src/bootstrap/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export interface SeedParams {
stackAPIKey?: string;
org?: string;
stackName?: string;
yes?: string;
yes?: boolean;
managementTokenAlias?: string | undefined;
managementToken?: string | undefined;
}
Expand Down Expand Up @@ -95,7 +95,7 @@ export default class Bootstrap {
cmd.push('-n', this.options.seedParams.stackName);
}
if (this.options.seedParams.yes) {
cmd.push('-y', this.options.seedParams.yes);
cmd.push('-y');
}
if (this.options.seedParams.managementTokenAlias) {
cmd.push('--alias', this.options.seedParams.managementTokenAlias);
Expand Down
11 changes: 6 additions & 5 deletions packages/contentstack-bootstrap/src/bootstrap/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ export const setupEnvironments = async (
if (!managementToken) {
const managementBody = {
token: {
name: 'sample app',
description: 'This is a sample management token.',
name: 'Compass Starter App',
description: 'This is a compass starter app management token.',
scope: [
{
module: 'content_type',
module: '$all',
acl: {
read: true,
write: true,
Expand All @@ -58,8 +58,9 @@ export const setupEnvironments = async (
},
},
],
expires_on: '3000-01-01',
expires_on: null,
is_email_notification_enabled: false,
rate_limit_enabled: false,
},
};
managementTokenResult = await managementAPIClient
Expand Down Expand Up @@ -313,7 +314,7 @@ const envFileHandler = async (
}CONTENTSTACK_ENVIRONMENT=${environmentVariables.environment}${!isUSRegion && !customHost ? '\nCONTENTSTACK_REGION=' + region.name : ''
}\nCONTENTSTACK_LIVE_PREVIEW=${livePreviewEnabled}\nCONTENTSTACK_LIVE_EDIT_TAGS=false\nCONTENTSTACK_API_HOST=${customHost ? customHost : managementAPIHost
}${!isUSRegion && !customHost ? '\nCONTENTSTACK_REGION=' + region.name : ''
}\nCONTENTSTACK_APP_HOST=${appHost}\nCONTENTSTACK_MANAGEMENT_TOKEN=${managementTokenResult.uid
}\nCONTENTSTACK_APP_HOST=${appHost}\nCONTENTSTACK_MANAGEMENT_TOKEN=${managementTokenResult.token
}\nCONTENTSTACK_HOST=${cdnHost}`;
result = await writeEnvFile(content, filePath);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export default class BootstrapCommand extends Command {
required: false,
exclusive: ['stack-api-key'],
}),
yes: flags.string({
yes: flags.boolean({
description: '[Optional] Skip stack confirmation',
char: 'y',
required: false,
Expand Down Expand Up @@ -123,7 +123,7 @@ export default class BootstrapCommand extends Command {
});
}

const yes = bootstrapCommandFlags.yes as string;
const yes = bootstrapCommandFlags.yes as boolean;

const appConfig: AppConfig = getAppLevelConfigByName(selectedAppName || selectedApp.configKey);
const master_locale = appConfig.master_locale || DEFAULT_MASTER_LOCALE;
Expand All @@ -147,7 +147,7 @@ export default class BootstrapCommand extends Command {
if (stackAPIKey) seedParams.stackAPIKey = stackAPIKey;
if (org) seedParams.org = org;
if (stackName) seedParams.stackName = stackName;
if (yes) seedParams.yes = yes;
if (yes) seedParams.yes = true;
if (managementTokenAlias) {
seedParams.managementTokenAlias = managementTokenAlias;
const listOfTokens = configHandler.get('tokens');
Expand Down
10 changes: 5 additions & 5 deletions packages/contentstack-seed/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,22 @@ To import content to your stack, you can choose from the following two sources:
<!-- usagestop -->
## Commands
<!-- commands -->
* [`csdx cm:stacks:seed [--repo <value>] [--org <value>] [--stack-api-key <value>] [--stack-name <value>] [--yes <value>] [--alias <value>] [--locale <value>]`](#csdx-cmstacksseed---repo-value---org-value---stack-api-key-value---stack-name-value---yes-value---alias-value---locale-value)
* [`csdx cm:stacks:seed [--repo <value>] [--org <value>] [--stack-api-key <value>] [--stack-name <value>] [-y] [--alias <value>] [--locale <value>]`](#csdx-cmstacksseed---repo-value---org-value---stack-api-key-value---stack-name-value---y---alias-value---locale-value)

## `csdx cm:stacks:seed [--repo <value>] [--org <value>] [--stack-api-key <value>] [--stack-name <value>] [--yes <value>] [--alias <value>] [--locale <value>]`
## `csdx cm:stacks:seed [--repo <value>] [--org <value>] [--stack-api-key <value>] [--stack-name <value>] [-y] [--alias <value>] [--locale <value>]`

Create a stack from existing content types, entries, assets, etc

```
USAGE
$ csdx cm:stacks:seed [--repo <value>] [--org <value>] [--stack-api-key <value>] [--stack-name <value>] [--yes
<value>] [--alias <value>] [--locale <value>]
$ csdx cm:stacks:seed [--repo <value>] [--org <value>] [--stack-api-key <value>] [--stack-name <value>] [-y]
[--alias <value>] [--locale <value>]

FLAGS
-a, --alias=<value> Alias of the management token
-k, --stack-api-key=<value> Provide stack API key to seed content to
-n, --stack-name=<value> Name of a new stack that needs to be created.
-y, --yes=<value> [Optional] Skip the stack confirmation.
-y, --yes [Optional] Skip the stack confirmation.
--org=<value> Provide Organization UID to create a new stack
--repo=<value> GitHub organization name or GitHub user name/repository name.

Expand Down
2 changes: 1 addition & 1 deletion packages/contentstack-seed/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@contentstack/cli-cm-seed",
"description": "create a Stack from existing content types, entries, assets, etc.",
"version": "2.0.0-beta.9",
"version": "2.0.0-beta.10",
"author": "Contentstack",
"bugs": "https://github.com/contentstack/cli/issues",
"dependencies": {
Expand Down
7 changes: 4 additions & 3 deletions packages/contentstack-seed/src/commands/cm/stacks/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default class SeedCommand extends Command {
];

static usage =
'cm:stacks:seed [--repo <value>] [--org <value>] [--stack-api-key <value>] [--stack-name <value>] [--yes <value>] [--alias <value>] [--locale <value>]';
'cm:stacks:seed [--repo <value>] [--org <value>] [--stack-api-key <value>] [--stack-name <value>] [-y] [--alias <value>] [--locale <value>]';

static flags: FlagInput = {
repo: flags.string({
Expand Down Expand Up @@ -49,7 +49,7 @@ export default class SeedCommand extends Command {
required: false,
hidden: true,
}),
yes: flags.string({
yes: flags.boolean({
char: 'y',
required: false,
description: '[Optional] Skip the stack confirmation.',
Expand All @@ -60,6 +60,7 @@ export default class SeedCommand extends Command {
}),
locale: flags.string({
description: 'Master Locale of the stack',
default: 'en-us',
hidden: true,
}),
};
Expand All @@ -84,7 +85,7 @@ export default class SeedCommand extends Command {
stackUid: seedFlags['stack-api-key'],
stackName: seedFlags['stack-name'],
fetchLimit: seedFlags['fetch-limit'],
skipStackConfirmation: seedFlags['yes'],
skipStackConfirmation: seedFlags.yes,
isAuthenticated: isAuthenticated(),
alias: managementTokenAlias,
master_locale: seedFlags['locale'],
Expand Down
20 changes: 20 additions & 0 deletions packages/contentstack-seed/src/seed/github/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,26 @@ export default class GitHubClient {
return false;
}

async getMasterLocaleFromRepo(repo: string): Promise<string | null> {
try {
const response = await this.httpClient.get(
`https://raw.githubusercontent.com/${this.username}/${repo}/main/stack/locales/master-locale.json`,
);

if (response.data) {
const localeData = response.data;
const localeKey = Object.keys(localeData)[0];
if (localeKey && localeData[localeKey]?.code) {
return localeData[localeKey].code;
}
}
} catch (error) {
console.log('Could not fetch master locale from repository', error);
}

return null;
}

async getLatestTarballUrl(repo: string) {
try {
const response = await this.httpClient.get(`${this.gitHubRepoUrl}/${repo}/releases/latest`);
Expand Down
9 changes: 8 additions & 1 deletion packages/contentstack-seed/src/seed/importer.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as fs from 'fs';
import * as process from 'process';
import * as path from 'path';
import ImportCommand from '@contentstack/cli-cm-import';
Expand All @@ -16,7 +17,13 @@ export interface ImporterOptions {
}

export async function run(options: ImporterOptions) {
const importPath = pathValidator(path.resolve(sanitizePath(options.tmpPath), STACK_FOLDER));
const tmpPathResolved = path.resolve(sanitizePath(options.tmpPath));
const stackPath = path.join(tmpPathResolved, STACK_FOLDER);

// Support both structures: repo with stack/ folder (per docs) or content at root
const importPath = fs.existsSync(stackPath)
? pathValidator(stackPath)
: pathValidator(tmpPathResolved);

const args = options.alias
? ['-k', options.api_key, '-d', importPath, '--alias', options.alias!]
Expand Down
52 changes: 12 additions & 40 deletions packages/contentstack-seed/src/seed/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export interface ContentModelSeederOptions {
stackUid: string | undefined;
stackName: string | undefined;
fetchLimit: string | undefined;
skipStackConfirmation: string | undefined;
skipStackConfirmation: boolean | undefined;
isAuthenticated: boolean | false;
managementToken?: string | undefined;
alias?: string | undefined;
Expand Down Expand Up @@ -181,52 +181,24 @@ export default class ContentModelSeeder {
}

async shouldProceed(api_key: string) {
let count;
const stack_details = await this.csClient.getStack(api_key);
if(this.options.master_locale != stack_details.master_locale){
cliux.print(`Compass app requires the master locale to be set to English (en).`,{
color: "yellow",
bold: true,
});
return false;
}
const managementBody = {
"name":"Checking roles for creating management token",
"description":"This is a compass app management token.",
"scope":[
{
"module":"content_type",
"acl":{
"read":true,
"write":true
}
},
{
"module":"branch",
"branches":[
"main"
],
"acl":{
"read":true
}
}
],
"expires_on": "3000-01-01",
"is_email_notification_enabled":false
}
let managementTokenResult = await this.csClient.createManagementToken(api_key, this.managementToken, managementBody);
if(managementTokenResult?.response_code == "161" || managementTokenResult?.response_code == "401"){
const repoMasterLocale = await this.ghClient.getMasterLocaleFromRepo(this.ghRepo as string);
const expectedLocale = repoMasterLocale || this.options.master_locale || ENGLISH_LOCALE;

if (stack_details.master_locale !== expectedLocale) {
cliux.print(
`Info: Failed to generate a management token.\nNote: Management token is not available in your plan. Please contact the admin for support.`,
`Repository '${this.ghRepo}' requires the master locale to be set to '${expectedLocale}', but your stack has '${stack_details.master_locale}'.`,
{
color: 'red',
color: 'yellow',
bold: true,
},
);
return false;
}
count = await this.csClient.getContentTypeCount(api_key, this.managementToken);
}

const count = await this.csClient.getContentTypeCount(api_key, this.managementToken);

if (count > 0 && this._options.skipStackConfirmation !== 'yes') {
if (count > 0 && !this._options.skipStackConfirmation) {
const proceed = await inquireProceed();

if (!proceed) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ describe('SeedCommand', () => {
'stack-api-key': undefined,
'stack-name': undefined,
'fetch-limit': undefined,
yes: 'yes',
yes: true,
alias: undefined,
locale: undefined,
};
Expand All @@ -239,7 +239,7 @@ describe('SeedCommand', () => {

expect(ContentModelSeeder).toHaveBeenCalledWith(
expect.objectContaining({
skipStackConfirmation: 'yes',
skipStackConfirmation: true,
}),
);
});
Expand All @@ -251,7 +251,7 @@ describe('SeedCommand', () => {
'stack-api-key': undefined,
'stack-name': 'My Stack',
'fetch-limit': '100',
yes: 'yes',
yes: true,
alias: 'my-alias',
locale: 'fr-fr',
};
Expand All @@ -273,7 +273,7 @@ describe('SeedCommand', () => {
stackUid: undefined,
stackName: 'My Stack',
fetchLimit: '100',
skipStackConfirmation: 'yes',
skipStackConfirmation: true,
isAuthenticated: true,
alias: 'my-alias',
master_locale: 'fr-fr',
Expand Down Expand Up @@ -349,7 +349,7 @@ describe('SeedCommand', () => {

it('should have correct usage', () => {
expect(SeedCommand.usage).toBe(
'cm:stacks:seed [--repo <value>] [--org <value>] [--stack-api-key <value>] [--stack-name <value>] [--yes <value>] [--alias <value>] [--locale <value>]',
'cm:stacks:seed [--repo <value>] [--org <value>] [--stack-api-key <value>] [--stack-name <value>] [-y] [--alias <value>] [--locale <value>]',
);
});

Expand Down
22 changes: 22 additions & 0 deletions packages/contentstack-seed/test/seed/importer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ jest.mock('@contentstack/cli-utilities', () => {
jest.mock('@contentstack/cli-cm-import');
jest.mock('@contentstack/cli-utilities');

import * as fs from 'fs';
import * as importer from '../../src/seed/importer';
import ImportCommand from '@contentstack/cli-cm-import';
import * as path from 'node:path';
Expand All @@ -37,6 +38,11 @@ describe('Importer', () => {
jest.spyOn(cliUtilities, 'pathValidator').mockImplementation((p: any) => p);
jest.spyOn(cliUtilities, 'sanitizePath').mockImplementation((p: any) => p);
(ImportCommand.run as jest.Mock) = jest.fn().mockResolvedValue(undefined);
// Mock fs.existsSync: stack folder exists (standard repo structure)
jest.spyOn(fs, 'existsSync').mockImplementation((checkPath: fs.PathLike) => {
const p = typeof checkPath === 'string' ? checkPath : checkPath.toString();
return p.endsWith('stack') || p.includes(path.sep + 'stack');
});
});

describe('run', () => {
Expand Down Expand Up @@ -176,5 +182,21 @@ describe('Importer', () => {
const expectedPath = path.resolve(mockOptions.tmpPath, 'stack');
expect(cliUtilities.pathValidator).toHaveBeenCalledWith(expectedPath);
});

it('should use tmpPath when stack folder does not exist (content at root)', async () => {
jest.spyOn(fs, 'existsSync').mockReturnValue(false);

await importer.run(mockOptions);

const expectedPath = path.resolve(mockOptions.tmpPath);
expect(cliUtilities.pathValidator).toHaveBeenCalledWith(expectedPath);
expect(ImportCommand.run).toHaveBeenCalledWith([
'-k',
mockOptions.api_key,
'-d',
expectedPath,
'--skip-audit',
]);
});
});
});
Loading
Loading