diff --git a/packages/contentstack-bootstrap/README.md b/packages/contentstack-bootstrap/README.md index 37b8076db..fdf90a201 100644 --- a/packages/contentstack-bootstrap/README.md +++ b/packages/contentstack-bootstrap/README.md @@ -15,7 +15,7 @@ $ npm install -g @contentstack/cli-cm-bootstrap $ csdx COMMAND running command... $ csdx (--version) -@contentstack/cli-cm-bootstrap/1.18.2 darwin-arm64 node-v24.13.0 +@contentstack/cli-cm-bootstrap/1.18.4 darwin-arm64 node-v22.14.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND @@ -44,14 +44,14 @@ Bootstrap contentstack apps ``` USAGE - $ csdx cm:bootstrap [--app-name ] [--project-dir ] [-k | --org | -n ] [-y - ] [--run-dev-server] [-a ] + $ csdx cm:bootstrap [--app-name ] [--project-dir ] [-k | --org | -n ] [-y] + [--run-dev-server] [-a ] FLAGS -a, --alias= Alias of the management token -k, --stack-api-key= Provide stack API key to seed content -n, --stack-name= Name of the new stack that will be created. - -y, --yes= [Optional] Skip stack confirmation + -y, --yes [Optional] Skip stack confirmation --app-name= App name, kickstart-next, kickstart-next-ssr, kickstart-next-ssg, kickstart-next-graphql, kickstart-next-middleware, kickstart-nuxt, kickstart-nuxt-ssr --org= Provide organization UID to create a new stack diff --git a/packages/contentstack-bootstrap/package.json b/packages/contentstack-bootstrap/package.json index 5e6d4793c..dec5f297a 100644 --- a/packages/contentstack-bootstrap/package.json +++ b/packages/contentstack-bootstrap/package.json @@ -1,7 +1,7 @@ { "name": "@contentstack/cli-cm-bootstrap", "description": "Bootstrap contentstack apps", - "version": "1.19.0-beta.0", + "version": "1.18.4", "author": "Contentstack", "bugs": "https://github.com/contentstack/cli/issues", "scripts": { @@ -16,10 +16,10 @@ "test:report": "nyc --reporter=lcov mocha \"test/**/*.test.js\"" }, "dependencies": { - "@contentstack/cli-cm-seed": "~1.15.0-beta.0", - "@contentstack/cli-command": "~1.8.0-beta.0", - "@contentstack/cli-config": "~1.20.0-beta.0", - "@contentstack/cli-utilities": "~1.18.0-beta.0", + "@contentstack/cli-cm-seed": "~1.14.3", + "@contentstack/cli-command": "~1.7.2", + "@contentstack/cli-config": "~1.19.0", + "@contentstack/cli-utilities": "~1.17.4", "@oclif/core": "^4.3.0", "@oclif/plugin-help": "^6.2.37", "inquirer": "8.2.7", diff --git a/packages/contentstack-bootstrap/src/bootstrap/index.ts b/packages/contentstack-bootstrap/src/bootstrap/index.ts index dca6a7220..c68bcdcaa 100644 --- a/packages/contentstack-bootstrap/src/bootstrap/index.ts +++ b/packages/contentstack-bootstrap/src/bootstrap/index.ts @@ -28,7 +28,7 @@ export interface SeedParams { stackAPIKey?: string; org?: string; stackName?: string; - yes?: string; + yes?: boolean; managementTokenAlias?: string | undefined; managementToken?: string | undefined; } @@ -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); diff --git a/packages/contentstack-bootstrap/src/bootstrap/utils.ts b/packages/contentstack-bootstrap/src/bootstrap/utils.ts index 164f9a1ad..cd1da33e4 100644 --- a/packages/contentstack-bootstrap/src/bootstrap/utils.ts +++ b/packages/contentstack-bootstrap/src/bootstrap/utils.ts @@ -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, @@ -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 @@ -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; diff --git a/packages/contentstack-bootstrap/src/commands/cm/bootstrap.ts b/packages/contentstack-bootstrap/src/commands/cm/bootstrap.ts index 8cf129cf3..431b402c5 100644 --- a/packages/contentstack-bootstrap/src/commands/cm/bootstrap.ts +++ b/packages/contentstack-bootstrap/src/commands/cm/bootstrap.ts @@ -73,7 +73,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, @@ -157,7 +157,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; @@ -181,7 +181,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'); diff --git a/packages/contentstack-seed/README.md b/packages/contentstack-seed/README.md index d2bb983a3..eb010e1dd 100644 --- a/packages/contentstack-seed/README.md +++ b/packages/contentstack-seed/README.md @@ -10,17 +10,17 @@ To import content to your stack, you can choose from the following two sources: ## Commands -* [`csdx cm:stacks:seed [--repo ] [--org ] [-k ] [-n ] [-y ] [-s ] [--locale ]`](#csdx-cmstacksseed---repo-value---org-value--k-value--n-value--y-value--s-value---locale-value) -* [`csdx cm:stacks:seed [--repo ] [--org ] [-k ] [-n ] [-y ] [-s ] [--locale ]`](#csdx-cmstacksseed---repo-value---org-value--k-value--n-value--y-value--s-value---locale-value) +* [`csdx cm:stacks:seed [--repo ] [--org ] [-k ] [-n ] [-y] [-s ] [--locale ]`](#csdx-cmstacksseed---repo-value---org-value--k-value--n-value--y--s-value---locale-value) +* [`csdx cm:stacks:seed [--repo ] [--org ] [-k ] [-n ] [-y] [-s ] [--locale ]`](#csdx-cmstacksseed---repo-value---org-value--k-value--n-value--y--s-value---locale-value) -## `csdx cm:stacks:seed [--repo ] [--org ] [-k ] [-n ] [-y ] [-s ] [--locale ]` +## `csdx cm:stacks:seed [--repo ] [--org ] [-k ] [-n ] [-y] [-s ] [--locale ]` Create a stack from existing content types, entries, assets, etc ``` USAGE - $ csdx cm:seed cm:stacks:seed [--repo ] [--org ] [-k ] [-n ] [-y ] [-s - ] [--locale ] + $ csdx cm:seed cm:stacks:seed [--repo ] [--org ] [-k ] [-n ] [-y] [-s ] + [--locale ] FLAGS -a, --alias= Alias of the management token @@ -29,7 +29,7 @@ FLAGS -o, --org= Provide Organization UID to create a new stack -r, --repo= GitHub organization name or GitHub user name/repository name. -s, --stack= Provide the stack UID to seed content. - -y, --yes= [Optional] Skip the stack confirmation. + -y, --yes [Optional] Skip the stack confirmation. DESCRIPTION Create a stack from existing content types, entries, assets, etc @@ -49,14 +49,14 @@ EXAMPLES $ csdx cm:stacks:seed --repo "account/repository" --org "your-org-uid" --stack-name "stack-name" //create a new stack in given org uid ``` -## `csdx cm:stacks:seed [--repo ] [--org ] [-k ] [-n ] [-y ] [-s ] [--locale ]` +## `csdx cm:stacks:seed [--repo ] [--org ] [-k ] [-n ] [-y] [-s ] [--locale ]` Create a stack from existing content types, entries, assets, etc ``` USAGE - $ csdx cm:stacks:seed [--repo ] [--org ] [-k ] [-n ] [-y ] [-s ] - [--locale ] + $ csdx cm:stacks:seed [--repo ] [--org ] [-k ] [-n ] [-y] [-s ] [--locale + ] FLAGS -a, --alias= Alias of the management token @@ -65,7 +65,7 @@ FLAGS -o, --org= Provide Organization UID to create a new stack -r, --repo= GitHub organization name or GitHub user name/repository name. -s, --stack= Provide the stack UID to seed content. - -y, --yes= [Optional] Skip the stack confirmation. + -y, --yes [Optional] Skip the stack confirmation. DESCRIPTION Create a stack from existing content types, entries, assets, etc diff --git a/packages/contentstack-seed/src/commands/cm/stacks/seed.ts b/packages/contentstack-seed/src/commands/cm/stacks/seed.ts index 44edbcbe7..da6563ce9 100644 --- a/packages/contentstack-seed/src/commands/cm/stacks/seed.ts +++ b/packages/contentstack-seed/src/commands/cm/stacks/seed.ts @@ -20,7 +20,7 @@ export default class SeedCommand extends Command { '$ csdx cm:stacks:seed --repo "account/repository" --org "your-org-uid" --stack-name "stack-name" //create a new stack in given org uid', ]; - static usage = 'cm:stacks:seed [--repo ] [--org ] [-k ] [-n ] [-y ] [-s ] [--locale ]'; + static usage = 'cm:stacks:seed [--repo ] [--org ] [-k ] [-n ] [-y] [-s ] [--locale ]'; static flags: FlagInput = { repo: flags.string({ @@ -59,7 +59,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.', @@ -80,6 +80,7 @@ export default class SeedCommand extends Command { }), locale: flags.string({ description: 'Master Locale of the stack', + default: 'en-us', hidden: true, }), }; @@ -106,7 +107,7 @@ export default class SeedCommand extends Command { stackUid: seedFlags['stack-api-key'] || seedFlags.stack, stackName: seedFlags['stack-name'], fetchLimit: seedFlags['fetch-limit'], - skipStackConfirmation: seedFlags['yes'], + skipStackConfirmation: seedFlags.yes, isAuthenticated: isAuthenticated(), alias: managementTokenAlias, master_locale: seedFlags['locale'], diff --git a/packages/contentstack-seed/src/seed/github/client.ts b/packages/contentstack-seed/src/seed/github/client.ts index 2bd9a7013..066913fb7 100644 --- a/packages/contentstack-seed/src/seed/github/client.ts +++ b/packages/contentstack-seed/src/seed/github/client.ts @@ -118,6 +118,26 @@ export default class GitHubClient { return false; } + async getMasterLocaleFromRepo(repo: string): Promise { + 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`); diff --git a/packages/contentstack-seed/src/seed/importer.ts b/packages/contentstack-seed/src/seed/importer.ts index ddeece2f4..e3b8b2b9b 100644 --- a/packages/contentstack-seed/src/seed/importer.ts +++ b/packages/contentstack-seed/src/seed/importer.ts @@ -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'; @@ -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!] diff --git a/packages/contentstack-seed/src/seed/index.ts b/packages/contentstack-seed/src/seed/index.ts index 4223acb0a..31e6fb9aa 100644 --- a/packages/contentstack-seed/src/seed/index.ts +++ b/packages/contentstack-seed/src/seed/index.ts @@ -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; @@ -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) { diff --git a/packages/contentstack-seed/tests/seeder.test.ts b/packages/contentstack-seed/tests/seeder.test.ts index 10d1e2d7d..c9ca02f57 100644 --- a/packages/contentstack-seed/tests/seeder.test.ts +++ b/packages/contentstack-seed/tests/seeder.test.ts @@ -67,6 +67,8 @@ describe('ContentModelSeeder', () => { }); test('should automatically proceed when no content types', async () => { + ContentstackClient.prototype.getStack = jest.fn().mockResolvedValue({ master_locale: 'en-us' }); + GitHubClient.prototype.getMasterLocaleFromRepo = jest.fn().mockResolvedValue(null); ContentstackClient.prototype.getContentTypeCount = jest.fn().mockResolvedValue(0); const seeder = new ContentModelSeeder(options); @@ -76,6 +78,8 @@ describe('ContentModelSeeder', () => { }); test('should not proceed when content types exists and user cancels', async () => { + ContentstackClient.prototype.getStack = jest.fn().mockResolvedValue({ master_locale: 'en-us' }); + GitHubClient.prototype.getMasterLocaleFromRepo = jest.fn().mockResolvedValue(null); ContentstackClient.prototype.getContentTypeCount = jest.fn().mockResolvedValue(1); // @ts-ignore @@ -88,6 +92,8 @@ describe('ContentModelSeeder', () => { }); test('should proceed when content types exists and user accepts risk', async () => { + ContentstackClient.prototype.getStack = jest.fn().mockResolvedValue({ master_locale: 'en-us' }); + GitHubClient.prototype.getMasterLocaleFromRepo = jest.fn().mockResolvedValue(null); ContentstackClient.prototype.getContentTypeCount = jest.fn().mockResolvedValue(1); // @ts-ignore @@ -99,6 +105,19 @@ describe('ContentModelSeeder', () => { expect(proceed).toBe(true); }); + test('should skip confirmation when skipStackConfirmation is true', async () => { + ContentstackClient.prototype.getStack = jest.fn().mockResolvedValue({ master_locale: 'en-us' }); + GitHubClient.prototype.getMasterLocaleFromRepo = jest.fn().mockResolvedValue(null); + ContentstackClient.prototype.getContentTypeCount = jest.fn().mockResolvedValue(1); + + const optionsWithSkip = { ...options, skipStackConfirmation: true }; + const seeder = new ContentModelSeeder(optionsWithSkip); + const proceed = await seeder.shouldProceed(api_key); + + expect(proceed).toBe(true); + expect(inquireProceed).not.toHaveBeenCalled(); + }); + test('should create stack', async () => { ContentstackClient.prototype.createStack = jest.fn().mockResolvedValue({ api_key: api_key, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2d9e588ce..abb181a56 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2023,6 +2023,9 @@ packages: '@sinonjs/text-encoding@0.7.3': resolution: {integrity: sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==} + deprecated: |- + Deprecated: no longer maintained and no longer used by Sinon packages. See + https://github.com/sinonjs/nise/issues/243 for replacement details. '@smithy/abort-controller@4.2.10': resolution: {integrity: sha512-qocxM/X4XGATqQtUkbE9SPUB6wekBi+FyJOMbPj0AhvyvFGYEmOlz6VB22iMePCQsFmMIvFSeViDvA7mZJG47g==} @@ -4063,6 +4066,7 @@ packages: fs-then-native@2.0.0: resolution: {integrity: sha512-X712jAOaWXkemQCAmWeg5rOT2i+KOpWz1Z/txk/cW0qlOu2oQ9H61vc5w3X/iyuUEfq/OyaFJ78/cZAQD1/bgA==} engines: {node: '>=4.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -6194,10 +6198,12 @@ packages: test-value@2.1.0: resolution: {integrity: sha512-+1epbAxtKeXttkGFMTX9H42oqzOTufR1ceCF+GYA5aOmvaPq9wd4PUS8329fn2RRLGNeUkgRLnVpycjx8DsO2w==} engines: {node: '>=0.10.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. test-value@3.0.0: resolution: {integrity: sha512-sVACdAWcZkSU9x7AOmJo5TqE+GyNJknHaHsMrR6ZnhjVlVN9Yx6FjHrsKZ3BjIpPCT68zYesPWkakrNupwfOTQ==} engines: {node: '>=4.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. text-hex@1.0.0: resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==}